From 8593beff8c21bd3d3e417ac893b9f317f5386070 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Fri, 4 Oct 2024 23:29:34 +0800 Subject: [PATCH] first commit --- .dockerignore | 5 + .gitignore | 4 + .gitmodules | 15 + Cargo.lock | 1219 +++ Cargo.toml | 9 + Dockerfile | 12 + LICENSE.txt | 5 + README.md | 225 + clippy.toml | 1 + logo.svg | 139 + vhdl_lang/Cargo.toml | 46 + vhdl_lang/benches/benchmark.rs | 95 + vhdl_lang/src/analysis.rs | 36 + vhdl_lang/src/analysis/analyze.rs | 492 ++ vhdl_lang/src/analysis/assignment.rs | 151 + vhdl_lang/src/analysis/association.rs | 665 ++ vhdl_lang/src/analysis/concurrent.rs | 483 ++ vhdl_lang/src/analysis/declarative.rs | 1325 +++ vhdl_lang/src/analysis/design_unit.rs | 747 ++ vhdl_lang/src/analysis/expression.rs | 1676 ++++ vhdl_lang/src/analysis/literals.rs | 228 + vhdl_lang/src/analysis/lock.rs | 204 + vhdl_lang/src/analysis/names.rs | 3427 ++++++++ vhdl_lang/src/analysis/overloaded.rs | 769 ++ vhdl_lang/src/analysis/package_instance.rs | 627 ++ vhdl_lang/src/analysis/range.rs | 663 ++ vhdl_lang/src/analysis/root.rs | 1490 ++++ vhdl_lang/src/analysis/scope.rs | 405 + vhdl_lang/src/analysis/semantic.rs | 253 + vhdl_lang/src/analysis/sequential.rs | 445 + vhdl_lang/src/analysis/standard.rs | 1140 +++ vhdl_lang/src/analysis/static_expression.rs | 481 ++ vhdl_lang/src/analysis/subprogram.rs | 497 ++ vhdl_lang/src/analysis/target.rs | 89 + .../analysis/tests/assignment_typecheck.rs | 682 ++ .../src/analysis/tests/association_formal.rs | 746 ++ .../analysis/tests/circular_dependencies.rs | 309 + .../src/analysis/tests/context_clause.rs | 1065 +++ .../src/analysis/tests/custom_attributes.rs | 329 + vhdl_lang/src/analysis/tests/declarations.rs | 102 + .../src/analysis/tests/deferred_constant.rs | 158 + vhdl_lang/src/analysis/tests/hierarchy.rs | 426 + vhdl_lang/src/analysis/tests/homographs.rs | 1069 +++ vhdl_lang/src/analysis/tests/implicit.rs | 285 + .../src/analysis/tests/incomplete_type.rs | 183 + .../analysis/tests/incremental_analysis.rs | 357 + vhdl_lang/src/analysis/tests/mod.rs | 142 + .../src/analysis/tests/package_instance.rs | 687 ++ .../src/analysis/tests/protected_type.rs | 363 + .../analysis/tests/resolves_design_units.rs | 550 ++ .../src/analysis/tests/resolves_names.rs | 2187 +++++ .../src/analysis/tests/resolves_type_mark.rs | 513 ++ .../src/analysis/tests/sensitivity_list.rs | 121 + .../analysis/tests/subprogram_arguments.rs | 432 + .../src/analysis/tests/subprogram_instance.rs | 558 ++ .../src/analysis/tests/tool_directive.rs | 33 + .../analysis/tests/typecheck_expression.rs | 1996 +++++ vhdl_lang/src/analysis/tests/util.rs | 255 + .../src/analysis/tests/view_declarations.rs | 619 ++ vhdl_lang/src/analysis/tests/visibility.rs | 792 ++ vhdl_lang/src/analysis/types.rs | 670 ++ vhdl_lang/src/ast.rs | 1649 ++++ vhdl_lang/src/ast/any_design_unit.rs | 232 + vhdl_lang/src/ast/ast_span.rs | 83 + vhdl_lang/src/ast/display.rs | 2343 ++++++ vhdl_lang/src/ast/search.rs | 2049 +++++ vhdl_lang/src/ast/token_range.rs | 121 + vhdl_lang/src/ast/util.rs | 564 ++ vhdl_lang/src/completion.rs | 113 + vhdl_lang/src/completion/attributes.rs | 189 + .../src/completion/entity_instantiation.rs | 369 + vhdl_lang/src/completion/generic.rs | 154 + vhdl_lang/src/completion/libraries.rs | 41 + vhdl_lang/src/completion/map_aspect.rs | 353 + vhdl_lang/src/completion/region.rs | 47 + vhdl_lang/src/completion/selected.rs | 204 + vhdl_lang/src/completion/tokenizer.rs | 111 + vhdl_lang/src/config.rs | 800 ++ vhdl_lang/src/data.rs | 22 + vhdl_lang/src/data/contents.rs | 632 ++ vhdl_lang/src/data/diagnostic.rs | 238 + vhdl_lang/src/data/error_codes.rs | 586 ++ vhdl_lang/src/data/latin_1.rs | 267 + vhdl_lang/src/data/message.rs | 99 + vhdl_lang/src/data/source.rs | 803 ++ vhdl_lang/src/data/symbol_table.rs | 238 + vhdl_lang/src/formatting/architecture.rs | 133 + vhdl_lang/src/formatting/buffer.rs | 330 + .../src/formatting/concurrent_statement.rs | 928 +++ vhdl_lang/src/formatting/configuration.rs | 378 + vhdl_lang/src/formatting/constraint.rs | 167 + vhdl_lang/src/formatting/context.rs | 51 + vhdl_lang/src/formatting/declaration.rs | 865 ++ vhdl_lang/src/formatting/design.rs | 304 + vhdl_lang/src/formatting/entity.rs | 187 + vhdl_lang/src/formatting/expression.rs | 286 + vhdl_lang/src/formatting/interface.rs | 389 + vhdl_lang/src/formatting/mod.rs | 122 + vhdl_lang/src/formatting/name.rs | 192 + .../src/formatting/sequential_statement.rs | 671 ++ vhdl_lang/src/formatting/statement.rs | 26 + vhdl_lang/src/formatting/subprogram.rs | 318 + vhdl_lang/src/formatting/token.rs | 50 + vhdl_lang/src/lib.rs | 47 + vhdl_lang/src/lint.rs | 7 + vhdl_lang/src/lint/dead_code.rs | 771 ++ vhdl_lang/src/main.rs | 142 + vhdl_lang/src/named_entity.rs | 858 ++ vhdl_lang/src/named_entity/arena.rs | 405 + vhdl_lang/src/named_entity/attribute.rs | 49 + vhdl_lang/src/named_entity/design.rs | 116 + vhdl_lang/src/named_entity/formal_region.rs | 347 + vhdl_lang/src/named_entity/object.rs | 213 + vhdl_lang/src/named_entity/overloaded.rs | 330 + vhdl_lang/src/named_entity/region.rs | 445 + vhdl_lang/src/named_entity/types.rs | 507 ++ vhdl_lang/src/named_entity/visibility.rs | 282 + vhdl_lang/src/project.rs | 706 ++ vhdl_lang/src/standard.rs | 37 + vhdl_lang/src/syntax.rs | 38 + vhdl_lang/src/syntax/alias_declaration.rs | 155 + vhdl_lang/src/syntax/attributes.rs | 278 + vhdl_lang/src/syntax/common.rs | 80 + vhdl_lang/src/syntax/component_declaration.rs | 371 + vhdl_lang/src/syntax/concurrent_statement.rs | 2244 ++++++ vhdl_lang/src/syntax/configuration.rs | 1091 +++ vhdl_lang/src/syntax/context.rs | 373 + vhdl_lang/src/syntax/declarative_part.rs | 327 + vhdl_lang/src/syntax/design_unit.rs | 982 +++ vhdl_lang/src/syntax/expression.rs | 1427 ++++ vhdl_lang/src/syntax/interface_declaration.rs | 1240 +++ vhdl_lang/src/syntax/names.rs | 1302 +++ vhdl_lang/src/syntax/object_declaration.rs | 375 + vhdl_lang/src/syntax/parser.rs | 89 + vhdl_lang/src/syntax/range.rs | 281 + vhdl_lang/src/syntax/recover.rs | 108 + vhdl_lang/src/syntax/separated_list.rs | 258 + vhdl_lang/src/syntax/sequential_statement.rs | 1939 +++++ vhdl_lang/src/syntax/subprogram.rs | 1074 +++ vhdl_lang/src/syntax/subtype_indication.rs | 659 ++ vhdl_lang/src/syntax/test.rs | 1035 +++ vhdl_lang/src/syntax/tokens.rs | 14 + vhdl_lang/src/syntax/tokens/keywords.rs | 471 ++ vhdl_lang/src/syntax/tokens/tokenizer.rs | 3109 +++++++ vhdl_lang/src/syntax/tokens/tokenstream.rs | 588 ++ vhdl_lang/src/syntax/type_declaration.rs | 1003 +++ vhdl_lang/src/syntax/view.rs | 333 + vhdl_lang/src/syntax/waveform.rs | 164 + vhdl_lang/tests/format_example_project.rs | 81 + vhdl_lang/tests/integration_tests.rs | 60 + .../tests/unused_declarations/my_entity.vhd | 15 + .../tests/unused_declarations/vhdl_ls.toml | 6 + vhdl_lang_macros/Cargo.toml | 21 + vhdl_lang_macros/src/lib.rs | 20 + vhdl_lang_macros/src/token_span_attribute.rs | 60 + vhdl_lang_macros/src/token_span_derive.rs | 119 + vhdl_libraries/ieee2008/README.ieee | 30 + .../ieee2008/fixed_float_types.vhdl | 61 + .../ieee2008/fixed_generic_pkg-body.vhdl | 6361 +++++++++++++++ .../ieee2008/fixed_generic_pkg.vhdl | 1439 ++++ vhdl_libraries/ieee2008/fixed_pkg.vhdl | 52 + .../ieee2008/float_generic_pkg-body.vhdl | 5712 +++++++++++++ .../ieee2008/float_generic_pkg.vhdl | 1000 +++ vhdl_libraries/ieee2008/float_pkg.vhdl | 55 + vhdl_libraries/ieee2008/ieee_bit_context.vhdl | 4 + vhdl_libraries/ieee2008/ieee_std_context.vhdl | 5 + .../ieee2008/math_complex-body.vhdl | 1602 ++++ vhdl_libraries/ieee2008/math_complex.vhdl | 1083 +++ vhdl_libraries/ieee2008/math_real-body.vhdl | 1927 +++++ vhdl_libraries/ieee2008/math_real.vhdl | 625 ++ vhdl_libraries/ieee2008/numeric_bit-body.vhdl | 3037 +++++++ vhdl_libraries/ieee2008/numeric_bit.vhdl | 1592 ++++ .../ieee2008/numeric_bit_unsigned-body.vhdl | 592 ++ .../ieee2008/numeric_bit_unsigned.vhdl | 623 ++ vhdl_libraries/ieee2008/numeric_std-body.vhdl | 4088 ++++++++++ vhdl_libraries/ieee2008/numeric_std.vhdl | 1685 ++++ .../ieee2008/numeric_std_unsigned-body.vhdl | 595 ++ .../ieee2008/numeric_std_unsigned.vhdl | 616 ++ .../ieee2008/std_logic_1164-body.vhdl | 1572 ++++ vhdl_libraries/ieee2008/std_logic_1164.vhdl | 309 + vhdl_libraries/ieee2008/std_logic_textio.vhdl | 3 + vhdl_libraries/std/env.vhd | 11 + vhdl_libraries/std/standard.vhd | 94 + vhdl_libraries/std/textio.vhd | 73 + vhdl_libraries/synopsys/std_logic_arith.vhdl | 2391 ++++++ vhdl_libraries/synopsys/std_logic_misc.vhdl | 967 +++ vhdl_libraries/synopsys/std_logic_signed.vhdl | 343 + .../synopsys/std_logic_unsigned.vhdl | 329 + vhdl_libraries/vhdl_ls.toml | 7 + vhdl_libraries/vital2000/memory_b.vhdl | 7151 +++++++++++++++++ vhdl_libraries/vital2000/memory_p.vhdl | 1729 ++++ vhdl_libraries/vital2000/prmtvs_b.vhdl | 5622 +++++++++++++ vhdl_libraries/vital2000/prmtvs_p.vhdl | 1413 ++++ vhdl_libraries/vital2000/timing_b.vhdl | 2187 +++++ vhdl_libraries/vital2000/timing_p.vhdl | 1202 +++ vhdl_ls/Cargo.toml | 35 + vhdl_ls/src/lib.rs | 15 + vhdl_ls/src/main.rs | 33 + vhdl_ls/src/rpc_channel.rs | 265 + vhdl_ls/src/stdio_server.rs | 292 + vhdl_ls/src/vhdl_server.rs | 999 +++ vhdl_ls/src/vhdl_server/completion.rs | 226 + vhdl_ls/src/vhdl_server/diagnostics.rs | 237 + vhdl_ls/src/vhdl_server/lifecycle.rs | 103 + vhdl_ls/src/vhdl_server/rename.rs | 58 + vhdl_ls/src/vhdl_server/text_document.rs | 166 + vhdl_ls/src/vhdl_server/workspace.rs | 111 + 207 files changed, 139180 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 clippy.toml create mode 100644 logo.svg create mode 100644 vhdl_lang/Cargo.toml create mode 100644 vhdl_lang/benches/benchmark.rs create mode 100644 vhdl_lang/src/analysis.rs create mode 100644 vhdl_lang/src/analysis/analyze.rs create mode 100644 vhdl_lang/src/analysis/assignment.rs create mode 100644 vhdl_lang/src/analysis/association.rs create mode 100644 vhdl_lang/src/analysis/concurrent.rs create mode 100644 vhdl_lang/src/analysis/declarative.rs create mode 100644 vhdl_lang/src/analysis/design_unit.rs create mode 100644 vhdl_lang/src/analysis/expression.rs create mode 100644 vhdl_lang/src/analysis/literals.rs create mode 100644 vhdl_lang/src/analysis/lock.rs create mode 100644 vhdl_lang/src/analysis/names.rs create mode 100644 vhdl_lang/src/analysis/overloaded.rs create mode 100644 vhdl_lang/src/analysis/package_instance.rs create mode 100644 vhdl_lang/src/analysis/range.rs create mode 100644 vhdl_lang/src/analysis/root.rs create mode 100644 vhdl_lang/src/analysis/scope.rs create mode 100644 vhdl_lang/src/analysis/semantic.rs create mode 100644 vhdl_lang/src/analysis/sequential.rs create mode 100644 vhdl_lang/src/analysis/standard.rs create mode 100644 vhdl_lang/src/analysis/static_expression.rs create mode 100644 vhdl_lang/src/analysis/subprogram.rs create mode 100644 vhdl_lang/src/analysis/target.rs create mode 100644 vhdl_lang/src/analysis/tests/assignment_typecheck.rs create mode 100644 vhdl_lang/src/analysis/tests/association_formal.rs create mode 100644 vhdl_lang/src/analysis/tests/circular_dependencies.rs create mode 100644 vhdl_lang/src/analysis/tests/context_clause.rs create mode 100644 vhdl_lang/src/analysis/tests/custom_attributes.rs create mode 100644 vhdl_lang/src/analysis/tests/declarations.rs create mode 100644 vhdl_lang/src/analysis/tests/deferred_constant.rs create mode 100644 vhdl_lang/src/analysis/tests/hierarchy.rs create mode 100644 vhdl_lang/src/analysis/tests/homographs.rs create mode 100644 vhdl_lang/src/analysis/tests/implicit.rs create mode 100644 vhdl_lang/src/analysis/tests/incomplete_type.rs create mode 100644 vhdl_lang/src/analysis/tests/incremental_analysis.rs create mode 100644 vhdl_lang/src/analysis/tests/mod.rs create mode 100644 vhdl_lang/src/analysis/tests/package_instance.rs create mode 100644 vhdl_lang/src/analysis/tests/protected_type.rs create mode 100644 vhdl_lang/src/analysis/tests/resolves_design_units.rs create mode 100644 vhdl_lang/src/analysis/tests/resolves_names.rs create mode 100644 vhdl_lang/src/analysis/tests/resolves_type_mark.rs create mode 100644 vhdl_lang/src/analysis/tests/sensitivity_list.rs create mode 100644 vhdl_lang/src/analysis/tests/subprogram_arguments.rs create mode 100644 vhdl_lang/src/analysis/tests/subprogram_instance.rs create mode 100644 vhdl_lang/src/analysis/tests/tool_directive.rs create mode 100644 vhdl_lang/src/analysis/tests/typecheck_expression.rs create mode 100644 vhdl_lang/src/analysis/tests/util.rs create mode 100644 vhdl_lang/src/analysis/tests/view_declarations.rs create mode 100644 vhdl_lang/src/analysis/tests/visibility.rs create mode 100644 vhdl_lang/src/analysis/types.rs create mode 100644 vhdl_lang/src/ast.rs create mode 100644 vhdl_lang/src/ast/any_design_unit.rs create mode 100644 vhdl_lang/src/ast/ast_span.rs create mode 100644 vhdl_lang/src/ast/display.rs create mode 100644 vhdl_lang/src/ast/search.rs create mode 100644 vhdl_lang/src/ast/token_range.rs create mode 100644 vhdl_lang/src/ast/util.rs create mode 100644 vhdl_lang/src/completion.rs create mode 100644 vhdl_lang/src/completion/attributes.rs create mode 100644 vhdl_lang/src/completion/entity_instantiation.rs create mode 100644 vhdl_lang/src/completion/generic.rs create mode 100644 vhdl_lang/src/completion/libraries.rs create mode 100644 vhdl_lang/src/completion/map_aspect.rs create mode 100644 vhdl_lang/src/completion/region.rs create mode 100644 vhdl_lang/src/completion/selected.rs create mode 100644 vhdl_lang/src/completion/tokenizer.rs create mode 100644 vhdl_lang/src/config.rs create mode 100644 vhdl_lang/src/data.rs create mode 100644 vhdl_lang/src/data/contents.rs create mode 100644 vhdl_lang/src/data/diagnostic.rs create mode 100644 vhdl_lang/src/data/error_codes.rs create mode 100644 vhdl_lang/src/data/latin_1.rs create mode 100644 vhdl_lang/src/data/message.rs create mode 100644 vhdl_lang/src/data/source.rs create mode 100644 vhdl_lang/src/data/symbol_table.rs create mode 100644 vhdl_lang/src/formatting/architecture.rs create mode 100644 vhdl_lang/src/formatting/buffer.rs create mode 100644 vhdl_lang/src/formatting/concurrent_statement.rs create mode 100644 vhdl_lang/src/formatting/configuration.rs create mode 100644 vhdl_lang/src/formatting/constraint.rs create mode 100644 vhdl_lang/src/formatting/context.rs create mode 100644 vhdl_lang/src/formatting/declaration.rs create mode 100644 vhdl_lang/src/formatting/design.rs create mode 100644 vhdl_lang/src/formatting/entity.rs create mode 100644 vhdl_lang/src/formatting/expression.rs create mode 100644 vhdl_lang/src/formatting/interface.rs create mode 100644 vhdl_lang/src/formatting/mod.rs create mode 100644 vhdl_lang/src/formatting/name.rs create mode 100644 vhdl_lang/src/formatting/sequential_statement.rs create mode 100644 vhdl_lang/src/formatting/statement.rs create mode 100644 vhdl_lang/src/formatting/subprogram.rs create mode 100644 vhdl_lang/src/formatting/token.rs create mode 100644 vhdl_lang/src/lib.rs create mode 100644 vhdl_lang/src/lint.rs create mode 100644 vhdl_lang/src/lint/dead_code.rs create mode 100644 vhdl_lang/src/main.rs create mode 100644 vhdl_lang/src/named_entity.rs create mode 100644 vhdl_lang/src/named_entity/arena.rs create mode 100644 vhdl_lang/src/named_entity/attribute.rs create mode 100644 vhdl_lang/src/named_entity/design.rs create mode 100644 vhdl_lang/src/named_entity/formal_region.rs create mode 100644 vhdl_lang/src/named_entity/object.rs create mode 100644 vhdl_lang/src/named_entity/overloaded.rs create mode 100644 vhdl_lang/src/named_entity/region.rs create mode 100644 vhdl_lang/src/named_entity/types.rs create mode 100644 vhdl_lang/src/named_entity/visibility.rs create mode 100644 vhdl_lang/src/project.rs create mode 100644 vhdl_lang/src/standard.rs create mode 100644 vhdl_lang/src/syntax.rs create mode 100644 vhdl_lang/src/syntax/alias_declaration.rs create mode 100644 vhdl_lang/src/syntax/attributes.rs create mode 100644 vhdl_lang/src/syntax/common.rs create mode 100644 vhdl_lang/src/syntax/component_declaration.rs create mode 100644 vhdl_lang/src/syntax/concurrent_statement.rs create mode 100644 vhdl_lang/src/syntax/configuration.rs create mode 100644 vhdl_lang/src/syntax/context.rs create mode 100644 vhdl_lang/src/syntax/declarative_part.rs create mode 100644 vhdl_lang/src/syntax/design_unit.rs create mode 100644 vhdl_lang/src/syntax/expression.rs create mode 100644 vhdl_lang/src/syntax/interface_declaration.rs create mode 100644 vhdl_lang/src/syntax/names.rs create mode 100644 vhdl_lang/src/syntax/object_declaration.rs create mode 100644 vhdl_lang/src/syntax/parser.rs create mode 100644 vhdl_lang/src/syntax/range.rs create mode 100644 vhdl_lang/src/syntax/recover.rs create mode 100644 vhdl_lang/src/syntax/separated_list.rs create mode 100644 vhdl_lang/src/syntax/sequential_statement.rs create mode 100644 vhdl_lang/src/syntax/subprogram.rs create mode 100644 vhdl_lang/src/syntax/subtype_indication.rs create mode 100644 vhdl_lang/src/syntax/test.rs create mode 100644 vhdl_lang/src/syntax/tokens.rs create mode 100644 vhdl_lang/src/syntax/tokens/keywords.rs create mode 100644 vhdl_lang/src/syntax/tokens/tokenizer.rs create mode 100644 vhdl_lang/src/syntax/tokens/tokenstream.rs create mode 100644 vhdl_lang/src/syntax/type_declaration.rs create mode 100644 vhdl_lang/src/syntax/view.rs create mode 100644 vhdl_lang/src/syntax/waveform.rs create mode 100644 vhdl_lang/tests/format_example_project.rs create mode 100644 vhdl_lang/tests/integration_tests.rs create mode 100644 vhdl_lang/tests/unused_declarations/my_entity.vhd create mode 100644 vhdl_lang/tests/unused_declarations/vhdl_ls.toml create mode 100644 vhdl_lang_macros/Cargo.toml create mode 100644 vhdl_lang_macros/src/lib.rs create mode 100644 vhdl_lang_macros/src/token_span_attribute.rs create mode 100644 vhdl_lang_macros/src/token_span_derive.rs create mode 100644 vhdl_libraries/ieee2008/README.ieee create mode 100644 vhdl_libraries/ieee2008/fixed_float_types.vhdl create mode 100644 vhdl_libraries/ieee2008/fixed_generic_pkg-body.vhdl create mode 100644 vhdl_libraries/ieee2008/fixed_generic_pkg.vhdl create mode 100644 vhdl_libraries/ieee2008/fixed_pkg.vhdl create mode 100644 vhdl_libraries/ieee2008/float_generic_pkg-body.vhdl create mode 100644 vhdl_libraries/ieee2008/float_generic_pkg.vhdl create mode 100644 vhdl_libraries/ieee2008/float_pkg.vhdl create mode 100644 vhdl_libraries/ieee2008/ieee_bit_context.vhdl create mode 100644 vhdl_libraries/ieee2008/ieee_std_context.vhdl create mode 100644 vhdl_libraries/ieee2008/math_complex-body.vhdl create mode 100644 vhdl_libraries/ieee2008/math_complex.vhdl create mode 100644 vhdl_libraries/ieee2008/math_real-body.vhdl create mode 100644 vhdl_libraries/ieee2008/math_real.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_bit-body.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_bit.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_bit_unsigned-body.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_bit_unsigned.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_std-body.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_std.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_std_unsigned-body.vhdl create mode 100644 vhdl_libraries/ieee2008/numeric_std_unsigned.vhdl create mode 100644 vhdl_libraries/ieee2008/std_logic_1164-body.vhdl create mode 100644 vhdl_libraries/ieee2008/std_logic_1164.vhdl create mode 100644 vhdl_libraries/ieee2008/std_logic_textio.vhdl create mode 100644 vhdl_libraries/std/env.vhd create mode 100644 vhdl_libraries/std/standard.vhd create mode 100644 vhdl_libraries/std/textio.vhd create mode 100644 vhdl_libraries/synopsys/std_logic_arith.vhdl create mode 100644 vhdl_libraries/synopsys/std_logic_misc.vhdl create mode 100644 vhdl_libraries/synopsys/std_logic_signed.vhdl create mode 100644 vhdl_libraries/synopsys/std_logic_unsigned.vhdl create mode 100644 vhdl_libraries/vhdl_ls.toml create mode 100644 vhdl_libraries/vital2000/memory_b.vhdl create mode 100644 vhdl_libraries/vital2000/memory_p.vhdl create mode 100644 vhdl_libraries/vital2000/prmtvs_b.vhdl create mode 100644 vhdl_libraries/vital2000/prmtvs_p.vhdl create mode 100644 vhdl_libraries/vital2000/timing_b.vhdl create mode 100644 vhdl_libraries/vital2000/timing_p.vhdl create mode 100644 vhdl_ls/Cargo.toml create mode 100644 vhdl_ls/src/lib.rs create mode 100644 vhdl_ls/src/main.rs create mode 100644 vhdl_ls/src/rpc_channel.rs create mode 100644 vhdl_ls/src/stdio_server.rs create mode 100644 vhdl_ls/src/vhdl_server.rs create mode 100644 vhdl_ls/src/vhdl_server/completion.rs create mode 100644 vhdl_ls/src/vhdl_server/diagnostics.rs create mode 100644 vhdl_ls/src/vhdl_server/lifecycle.rs create mode 100644 vhdl_ls/src/vhdl_server/rename.rs create mode 100644 vhdl_ls/src/vhdl_server/text_document.rs create mode 100644 vhdl_ls/src/vhdl_server/workspace.rs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..85b22d6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.github +Dockerfile +example_project +target diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a8c4da --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +.vscode/ +**/*.rs.bk +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..026c377 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "example_project/UVVM"] + path = example_project/UVVM + url = https://github.com/UVVM/UVVM.git +[submodule "example_project/vunit"] + path = example_project/vunit + url = https://github.com/VUnit/vunit.git +[submodule "example_project/PoC"] + path = example_project/PoC + url = https://github.com/VLSI-EDA/PoC.git +[submodule "example_project/OSVVM"] + path = example_project/OSVVM + url = https://github.com/OSVVM/OSVVM.git +[submodule "example_project/neorv32"] + path = example_project/neorv32 + url = https://github.com/stnolting/neorv32.git diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b0826d2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1219 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "assert_cmd" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "brunch" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3932d710d985d35c7b08e7e439a6ac8607aa8f619d373eb1f808578cd3cd56e5" +dependencies = [ + "dactyl", + "unicode-width", +] + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "dactyl" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3464c10bbdaf3c0b4c9bdecb477d4cad8a6916454066dfe4f28c491dbb335b" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lsp-server" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248f65b78f6db5d8e1b1604b4098a28b43d21a8eb1deeca22b1c421b276c7095" +dependencies = [ + "crossbeam-channel", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "lsp-types" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pinned_vec" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268ad82d92622fb0a049ff14b01089b0f1bcd5c507fab44724394d328417348a" + +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subst" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266d3fe7ffc582b3a0c3fe36cdc88d5635a1c2d53e7c3f813c901d7bd1d34ba0" +dependencies = [ + "memchr", + "unicode-width", +] + +[[package]] +name = "syn" +version = "2.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vhdl_lang" +version = "0.83.0" +dependencies = [ + "assert_cmd", + "assert_matches", + "brunch", + "clap", + "dirs", + "dunce", + "enum-map", + "fnv", + "glob", + "itertools", + "pad", + "parking_lot", + "pinned_vec", + "predicates", + "pretty_assertions", + "rayon", + "strum", + "subst", + "tempfile", + "toml", + "vhdl_lang_macros", +] + +[[package]] +name = "vhdl_lang_macros" +version = "0.83.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "vhdl_ls" +version = "0.83.0" +dependencies = [ + "clap", + "env_logger", + "fnv", + "fuzzy-matcher", + "log", + "lsp-server", + "lsp-types", + "pretty_assertions", + "regex", + "serde", + "serde_json", + "tempfile", + "vhdl_lang", +] + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0e728bc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +[workspace] +resolver = "2" +members = ["vhdl_lang_macros", "vhdl_lang", "vhdl_ls"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..18ae7af --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +ARG RUST_VERSION=stable +FROM clux/muslrust:$RUST_VERSION as builder +WORKDIR /volume +COPY . /volume/ +ARG CRATE +RUN cargo build --manifest-path $CRATE/Cargo.toml --release + +FROM scratch +ARG CRATE +COPY --from=builder /volume/target/x86_64-unknown-linux-musl/release/$CRATE /app/bin/exe +COPY --from=builder /volume/vhdl_libraries /app/vhdl_libraries +ENTRYPOINT ["/app/bin/exe"] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6d65866 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,5 @@ +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this file, +You can obtain one at http://mozilla.org/MPL/2.0/. + +Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6d6bee --- /dev/null +++ b/README.md @@ -0,0 +1,225 @@ +# Overview + +This repository contains a fast VHDL language server and analysis library written in Rust. + +The speed makes the tool very pleasant to use since it loads projects really fast and does not consume a lot of ram. +A 200.000 line VHDL project is analyzed in 160 ms on my Desktop using 8 cores and only consumes 180 MByte of RAM when +loaded. + +I very much appreciate help from other people especially regarding semantic analysis of VHDL. You do not need to be a +programmer to help, it is even more helpful to interpret and clarify the VHDL standard and provide minimal examples and +describe how they should work according to the standard. Further information about contributing can be found by reading +the [Contributors Guide](https://github.com/kraigher/rust_hdl/wiki/Contributor-Guide) + +[![Chat](https://img.shields.io/matrix/VHDL-LS:matrix.org)](https://matrix.to/#/#VHDL-LS:matrix.org) +[![Build Status](https://github.com/kraigher/rust_hdl/workflows/Build%20%26%20test%20all%20configs/badge.svg)](https://github.com/kraigher/rust_hdl/actions?query=workflow%3A%22Build+%26+test+all+configs%22) + +## Contributors + +- Maintainer: [Lukas Scheller](https://github.com/Schottkyc137) +- Founder: [Olof Kraigher](https://github.com/kraigher) + +# Projects + +## VHDL Language Server + +[![vhdl ls crate](https://img.shields.io/crates/v/vhdl_ls.svg)](https://crates.io/crates/vhdl_ls) + +### Goals + +- A complete VHDL language server protocol implementation with diagnostics, navigate to symbol, find all references etc. + +### Features + +- Live syntax and type checking +- Checks for missing and duplicate declarations +- Supports goto-definition/declaration (also in presence of overloading) +- Supports find-references (also in presence of overloading) +- Supports goto-implementation + - From component declaration to matching entity by default binding + - From entity to matching component declaration by default binding +- Supports hovering symbols +- Rename symbol +- Find workspace symbols +- View/find document symbols + +## When Installing it from Crate + +When installing the VHDL_LS from [crates.io](https://crates.io/crates/vhdl_ls) the required +[vhdl_libraries](https://github.com/VHDL-LS/rust_hdl/tree/master/vhdl_libraries) directory will not be installed +automatically and +will need to be copied into the parent directory of the VHDL_LS binary manually. + +## Trying it out + +A language server is never used directly by the end user and it is integrated into different editor plugins. The ones I +know about are listed here. + +## Use in VSCode + +https://github.com/Bochlin/rust_hdl_vscode + +## Use in emacs + +VHDL LS has built-in support by emacs `lsp-mode` since 2020-01-04. + +It can be set up automatically by installing the package +[`vhdl-ext`](https://github.com/gmlarumbe/vhdl-ext/) and adding the +following snippet to your config: + +```elisp +(require 'vhdl-ext) +(vhdl-ext-mode-setup) +(vhdl-ext-eglot-set-server 've-rust-hdl) ;`eglot' config +(vhdl-ext-lsp-set-server 've-rust-hdl) ; `lsp' config +``` + +## Installation for Neovim + +### Automatic Installation + +You can install `rust_hdl` automatically in Neovim using [`:Mason`](https://github.com/williamboman/mason.nvim). Within +Mason, the package is called `rust_hdl`. If you don't have `:Mason`, you can simply install the binary as previously +described. + +### Automatic Configuration using `nvim-lspconfig` + +[`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) has a built in configuration +for [`vhdl_ls`](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#vhdl_ls) + +In order to configure it, simply add + +```lua +lspconfig = require('lspconfig') +lspconfig['vhdl_ls'].setup({ + on_attach = on_attach, + capabilities = capabilities +}) +``` + +### Manual Configuration using Neovim's built in client + +Neovim provides an LSP client to the VHDL_LS language server. Download the +VHDL_LS release. The binary must be on the path and executable (if you can run +"vhdl_ls -h" in the terminal then you're good). + +In your Neovim config.lua add the following: + +```lua +function STARTVHDLLS() + vim.lsp.start({ + name = 'vhdl_ls', + cmd = {'vhdl_ls'}, + }) +end +vim.api.nvim_set_keymap('n', '', ':lua STARTVHDLLS()', { noremap = true, silent = true }) +``` + +Using the example above, pressing F5 while inside Neovim starts the language +server. There are also other options, like automatically starting it when +opening a certain file type, see the [Neovim LSP documentation](https://neovim.io/doc/user/lsp.html) for more. + +## Configuration + +The language server needs to know your library mapping to perform full analysis of the code. For this it uses a +configuration file in the [TOML](https://github.com/toml-lang/toml) format named `vhdl_ls.toml`. + +`vhdl_ls` will load configuration files in the following order of priority (first to last): + +1. A file named `.vhdl_ls.toml` in the user home folder. +2. A file name from the `VHDL_LS_CONFIG` environment variable. +3. A file named `vhdl_ls.toml` in the workspace root. + +Settings in a later files overwrites those from previously loaded files. + +Define the VHDL revision to use for parsing and analysis with the `standard` key. +The expected value is the year associated the VHDL standard. +Supported standards are 1993, 2008 and 2019 where both the long version ("2008") and the short version ("08") can be +used. +If nothing is specified, 2008 is used. + +> [!NOTE] +> Defining the standard feature is a relatively new feature (since april 2024). +> Anything but the 2008 standard will not change much at the moment. + +**Example vhdl_ls.toml** + +```toml +# What standard to use. This is optional and defaults to VHDL2008. +standard = "2008" +# File names are either absolute or relative to the parent folder of the vhdl_ls.toml file +[libraries] +lib2.files = [ + 'pkg2.vhd', +] +lib1.files = [ + 'pkg1.vhd', + 'tb_ent.vhd' +] + +# Wildcards are supported +lib3.files = [ + 'test/*.vhd', + 'src/*.vhd', + 'src/*/*.vhd', +] + +# Libraries can be marked as third-party to disable some analysis warnings, such as unused declarations +UNISIM.files = [ + 'C:\Xilinx\Vivado\2023.1\data\vhdl\src\unisims\unisim_VCOMP.vhd', +] +UNISIM.is_third_party = true + +[lint] +unused = 'error' # Upgrade the 'unused' diagnostic to the 'error' severity +unnecessary_work_library = false # Disable linting for the 'library work;' statement +``` + +Using the `lint` table, you can configure the severity of diagnostics or turn of diagnostics altogether. + +> [!WARNING] +> You can overwrite every diagnostic error code including syntax or analysis errors using the lint table. +> However, the intended use-case is for lints only. +> Overwriting syntax or analysis errors (e.g., error codes `unused` or `syntax`) can cause unwanted side effects + +Paths in the `vhdl_ls.toml` can contain glob patterns (i.e., `.../*/`). +On Unix machines, they can contain environment variables using the `$NAME` or `${NAME}` syntax. +On Windows machines, use the `%NAME%` syntax to substitute environment variables. + +## As an LSP-client developer how should I integrate VHDL-LS? + +I recommend that the `lsp-client` polls GitHub and downloads +the [latest](https://github.com/VHDL-LS/rust_hdl/releases/latest) VHDL-LS release from GitHub. + +VHDL-LS has frequent releases and the automatic update ensures minimal maintenance for the `lsp-client` developer as +well as ensuring the users are not running and outdated version. + +## VHDL Language Frontend + +[![vhdl language frontend crate](https://img.shields.io/crates/v/vhdl_lang.svg)](https://crates.io/crates/vhdl_lang) + +### Goals + +- This project aims to provide a fully featured open source VHDL frontend that is easy to integrate into other tools. +- A design goal of the frontend is to be able to recover from syntax errors such that it is useful for building a + language server. +- Analysis order must be automatically computed such that the user does not have to maintain a compile order. +- Comments will be part of the AST to support document generation. +- Separate parsing from semantic analysis to allow code formatting on non-semantically correct code. + +## Building the project locally + +1) Make sure that you have the [Rust toolchain](https://www.rust-lang.org/tools/install) installed. + This repository always follows the latest toolchain in the `stable` channel. +2) Run `cargo install --path vhdl_lang` to install the language frontend. Run instead `cargo install --path vhdl_ls` + to install the language server. +3) Make sure that the default libraries are available at a visible path. Search paths are, for example, + `/usr/lib/rust_hdl/vhdl_libraries` or `/usr/local/lib/rust_hdl/vhdl_libraries`. +4) Run the command `vhdl_lang` or `vhdl_ls` to run the language front-end binary or the language server + +**Testing the Language Server** + +For checking the language server, [rust_hdl_vscode](https://github.com/VHDL-LS/rust_hdl_vscode) is recommended. +To instruct the extension to use the new binary, instead of a downloaded one, go to the extension settings and set +the Language server location to `systemPath`. To specify the exact path, set it to `user` and set Language Server User +Path to the path that points to the `vhdl_ls` binary. diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..df1d091 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +ignore-interior-mutability = ["vhdl_lang::data::source::UniqueSource"] diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..4adf19a --- /dev/null +++ b/logo.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + VHDL + LS + + + + + + diff --git a/vhdl_lang/Cargo.toml b/vhdl_lang/Cargo.toml new file mode 100644 index 0000000..b29851a --- /dev/null +++ b/vhdl_lang/Cargo.toml @@ -0,0 +1,46 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +[package] +name = "vhdl_lang" +version = "0.83.0" +authors = ["Olof Kraigher "] +license = "MPL-2.0" +description = "VHDL Language Frontend" +repository = "https://github.com/kraigher/rust_hdl" +edition = "2021" + +[dependencies] +vhdl_lang_macros = { version = "^0.83.0", path = "../vhdl_lang_macros" } +pad = "0" +fnv = "1" +clap = { version = "4", features = ["derive"] } +toml = "0" +glob = "0" +dirs = "5" +rayon = "1" +parking_lot = "0" +dunce = "1" +pinned_vec = "0" +itertools = "0" +subst = "0.3.0" +strum = { version = "0.26.2", features = ["derive"] } +enum-map = "2.7.3" + +[dev-dependencies] +tempfile = "3" +pretty_assertions = "1" +assert_matches = "1" +brunch = "0" +assert_cmd = "2.0.14" +predicates = "3.1.0" + +[[bench]] +name = "benchmark" +harness = false + +[features] +default = [] diff --git a/vhdl_lang/benches/benchmark.rs b/vhdl_lang/benches/benchmark.rs new file mode 100644 index 0000000..53edaf5 --- /dev/null +++ b/vhdl_lang/benches/benchmark.rs @@ -0,0 +1,95 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use brunch::{Bench, Benches}; +use std::{path::Path, time::Duration}; +use vhdl_lang::{ + ast::search::{SearchState, Searcher}, + Config, MessagePrinter, NullMessages, Project, +}; + +fn load_config(include_example_project: bool) -> Config { + let repo_root = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); + + let mut config = Config::default(); + config.append( + &Config::read_file_path(&repo_root.join("vhdl_libraries").join("vhdl_ls.toml")) + .expect("Failed to read installed config file"), + &mut MessagePrinter::default(), + ); + + if include_example_project { + config.append( + &Config::read_file_path(&repo_root.join("example_project").join("vhdl_ls.toml")) + .expect("Failed to read project config file"), + &mut MessagePrinter::default(), + ); + } + + config +} + +fn main() { + let mut benches = Benches::default(); + + { + // Only use standard libraries to benchmark parse and analyze as the time taken to get 100 samples + // is very big with the example project + let config = load_config(false); + benches.push(Bench::new("parse and analyze").with_samples(10).run(|| { + let mut project = Project::from_config(config.clone(), &mut NullMessages); + project.analyse(); + })); + } + + { + let mut project = Project::from_config(load_config(true), &mut NullMessages); + project.analyse(); + + let integer = project + .public_symbols() + .find(|ent| matches!(ent.designator().as_identifier(), Some(sym) if sym.name_utf8() == "INTEGER")) + .unwrap(); + + benches.push(Bench::new("find all references").run(|| { + assert!(!project.find_all_references(integer).is_empty()); + })); + + let integer_pos = integer.decl_pos().unwrap(); + benches.push(Bench::new("item at cursor").run(|| { + assert_eq!( + project + .item_at_cursor(&integer_pos.source, integer_pos.start()) + .unwrap() + .1, + integer + ); + })); + + benches.push( + Bench::new("search entire ast") + .with_timeout(Duration::from_secs(30)) + .run(|| { + project.search(&mut MySearcher {}); + }), + ); + } + + benches.finish(); +} + +struct MySearcher {} + +impl Searcher for MySearcher { + fn search_pos_with_ref( + &mut self, + _ctx: &dyn vhdl_lang::TokenAccess, + _pos: &vhdl_lang::SrcPos, + _ref: &vhdl_lang::Reference, + ) -> SearchState { + std::hint::black_box(SearchState::NotFinished) + } +} diff --git a/vhdl_lang/src/analysis.rs b/vhdl_lang/src/analysis.rs new file mode 100644 index 0000000..cd1c79b --- /dev/null +++ b/vhdl_lang/src/analysis.rs @@ -0,0 +1,36 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +#[macro_use] +mod analyze; +mod assignment; +mod association; +mod concurrent; +mod declarative; +mod design_unit; +mod expression; +mod literals; +mod lock; +mod names; +mod overloaded; +mod package_instance; +mod range; +mod root; +mod scope; +mod semantic; +mod sequential; +mod standard; +mod static_expression; +mod subprogram; +mod target; +mod types; + +#[cfg(test)] +pub(crate) mod tests; + +pub(crate) use root::{Library, LockedUnit}; + +pub use self::root::{DesignRoot, EntHierarchy}; diff --git a/vhdl_lang/src/analysis/analyze.rs b/vhdl_lang/src/analysis/analyze.rs new file mode 100644 index 0000000..bd366ea --- /dev/null +++ b/vhdl_lang/src/analysis/analyze.rs @@ -0,0 +1,492 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::root::*; +pub(crate) use super::scope::Scope; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use crate::syntax::TokenAccess; +use crate::TokenSpan; +use fnv::FnvHashSet; +use std::cell::RefCell; +use std::ops::Deref; + +/// Indicates that a circular dependency is found at the position denoted by `reference`. +/// +/// A circular dependency occurs when module A uses module B, which in turn +/// (either directly or indirectly via other modules) uses module A again. +/// +/// ## Example +/// +/// ```vhdl +/// use work.bar; +/// +/// package foo is +/// end package; +/// +/// use work.foo; +/// +/// package bar is +/// end package; +/// ``` +/// In this example, the package `bar` uses the package `foo` which in turn uses package `bar` – +/// making the dependency chain cyclic. +/// +/// Commonly, two or more `CircularDependencyError`s are pushed to indicate what modules +/// the error affects. +#[derive(Clone, Debug, PartialEq, Eq)] +#[must_use] +pub struct CircularDependencyError { + /// The position where the circular dependency was found. + /// Is `None` when the circular dependency is found in the standard library. + /// This should, in practice, never happen. + reference: Option, +} + +impl CircularDependencyError { + pub fn new(reference: Option<&SrcPos>) -> CircularDependencyError { + CircularDependencyError { + reference: reference.cloned(), + } + } + + /// Pushes this error into a diagnostic handler. + pub fn push_into(self, diagnostics: &mut dyn DiagnosticHandler) { + if let Some(pos) = self.reference { + diagnostics.push(Diagnostic::circular_dependency(pos)); + } + } +} + +/// A `FatalResult` is a result that is either OK or contains a `CircularDependencyError`. +/// If this type contains the error case, most other errors encountered during analysis are ignored +/// as analysis cannot continue (resp. no further analysis is pursued) +pub type FatalResult = Result; + +#[derive(Debug, PartialEq, Eq)] +pub enum EvalError { + /// A circular dependency was found, see [CircularDependencyError](CircularDependencyError) + Circular(CircularDependencyError), + /// Indicates that evaluation is no longer possible, for example if encountering illegal code. + /// Typically, functions returning Unknown will have published diagnostics on the side-channel + /// and the unknown is returned to stop further upstream analysis + Unknown, +} + +impl From for EvalError { + fn from(err: CircularDependencyError) -> EvalError { + EvalError::Circular(err) + } +} + +/// The result of the evaluation of an AST element. +/// The result has either a value of `Ok(T)`, indicating a successful evaluation of +/// the AST and returning the result of that evaluation, or `Err(EvalError)`, indicating +/// an error during evaluation. +/// +/// Most of the time, the error will be `EvalError::Unknown`. This means that the evaluation +/// step has pushed found problems in the code to some side-channel and simply returns an error, +/// signifying that some problem was found without further specifying that problem. +pub type EvalResult = Result; + +/// Pushes the diagnostic to the provided handler and returns +/// with an `EvalError::Unknown` result. +/// +/// This macro can be used for the common case of encountering an analysis error and +/// immediately returning as the error is not recoverable. +macro_rules! bail { + ($diagnostics:expr, $error:expr) => { + $diagnostics.push($error); + return Err(EvalError::Unknown); + }; +} + +pub trait IntoEvalResult { + /// Transforms `Self` into an `EvalResult`. + /// + /// If `Self` has any severe errors, these should be pushed to the provided handler + /// and an `Err(EvalError::Unknown)` should be returned. + fn into_eval_result(self, diagnostics: &mut dyn DiagnosticHandler) -> EvalResult; +} + +impl IntoEvalResult for Result { + fn into_eval_result(self, diagnostics: &mut dyn DiagnosticHandler) -> EvalResult { + match self { + Ok(value) => Ok(value), + Err(diagnostic) => { + bail!(diagnostics, diagnostic); + } + } + } +} + +pub fn as_fatal(res: EvalResult) -> FatalResult> { + match res { + Ok(val) => Ok(Some(val)), + Err(EvalError::Unknown) => Ok(None), + Err(EvalError::Circular(circ)) => Err(circ), + } +} + +pub(super) struct AnalyzeContext<'a, 't> { + pub(super) root: &'a DesignRoot, + + pub work_sym: Symbol, + std_sym: Symbol, + standard_sym: Symbol, + pub(super) is_std_logic_1164: bool, + + // Record dependencies and sensitives when + // analyzing design units + // + // Dependencies define the order in which design units must be analyzed + // - for example when doing 'use library.pkg' the pkg is a dependency + // + // Sensitivity defines conditions that require re-analysis of a design unit + // - for example when doing 'use library.missing' the file is sensitive to adding + // primary unit missing to library + // - for example when doing 'use missing' the file is sensitive to adding + // missing library + // - for example when doing 'use library.all' the file is sensitive to adding/removing + // anything from library + current_unit: UnitId, + source: Source, + pub(super) arena: &'a Arena, + uses: RefCell>, + missing_unit: RefCell)>>, + uses_library_all: RefCell>, + pub ctx: &'t dyn TokenAccess, +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn new( + root: &'a DesignRoot, + current_unit: &UnitId, + source: Source, + arena: &'a Arena, + ctx: &'t dyn TokenAccess, + ) -> AnalyzeContext<'a, 't> { + AnalyzeContext { + work_sym: root.symbol_utf8("work"), + std_sym: root.symbol_utf8("std"), + standard_sym: root.symbol_utf8("standard"), + is_std_logic_1164: current_unit + == &UnitId::package( + &root.symbol_utf8("ieee"), + &root.symbol_utf8("std_logic_1164"), + ), + root, + current_unit: current_unit.clone(), + source, + arena, + uses: RefCell::new(FnvHashSet::default()), + missing_unit: RefCell::new(FnvHashSet::default()), + uses_library_all: RefCell::new(FnvHashSet::default()), + ctx, + } + } + + pub fn work_library_name(&self) -> &Symbol { + self.current_unit.library_name() + } + + pub fn work_library(&self) -> EntRef<'a> { + self.get_library(self.current_unit.library_name()).unwrap() + } + + pub fn current_unit_id(&self) -> &UnitId { + &self.current_unit + } + + fn make_use_of(&self, use_pos: Option<&SrcPos>, unit_id: &UnitId) -> FatalResult { + // Check local cache before taking lock + if self.uses.borrow_mut().insert(unit_id.clone()) { + self.root.make_use_of(use_pos, &self.current_unit, unit_id) + } else { + Ok(()) + } + } + + fn make_use_of_library_all(&self, library_name: &Symbol) { + // Check local cache before taking lock + if self + .uses_library_all + .borrow_mut() + .insert(library_name.clone()) + { + self.root + .make_use_of_library_all(&self.current_unit, library_name); + } + } + + fn make_use_of_missing_unit( + &self, + library_name: &Symbol, + primary_name: &Symbol, + secondary_name: Option<&Symbol>, + ) { + let key = ( + library_name.clone(), + primary_name.clone(), + secondary_name.cloned(), + ); + + // Check local cache before taking lock + if self.missing_unit.borrow_mut().insert(key) { + self.root.make_use_of_missing_unit( + &self.current_unit, + library_name, + primary_name, + secondary_name, + ); + } + } + + pub fn use_all_in_library( + &self, + use_pos: &SrcPos, + library_name: &Symbol, + scope: &Scope<'a>, + ) -> FatalResult { + let units = self.root.get_library_units(library_name).unwrap(); + + for unit in units.values() { + match unit.kind() { + AnyKind::Primary(..) => { + let data = self.get_analysis(Some(use_pos), unit)?; + if let AnyDesignUnit::Primary(primary) = data.deref() { + if let Some(id) = primary.ent_id() { + scope.make_potentially_visible(Some(use_pos), self.arena.get(id)); + } + } + } + AnyKind::Secondary(..) => {} + } + } + + self.make_use_of_library_all(library_name); + Ok(()) + } + + /// Add implicit context clause for all packages except STD.STANDARD + /// library STD, WORK; + /// use STD.STANDARD.all; + pub fn add_implicit_context_clause(&self, scope: &Scope<'a>) -> FatalResult { + // work is not visible in context declarations + if self.current_unit.kind() != AnyKind::Primary(PrimaryKind::Context) { + scope.make_potentially_visible_with_name( + None, + self.work_sym.clone().into(), + self.work_library(), + ); + } + + if let Some(std_library) = self.get_library(&self.std_sym) { + scope.make_potentially_visible(None, std_library); + + let standard_region = self + .standard_package_region() + .expect("Expected standard package"); + scope.make_all_potentially_visible(None, standard_region); + } + + Ok(()) + } + + pub fn get_library(&self, library_name: &Symbol) -> Option> { + let (arena, id) = self.root.get_library_arena(library_name)?; + self.arena.link(arena); + Some(self.arena.get(id)) + } + + fn get_package_body(&self) -> Option<&'a LockedUnit> { + let units = self.root.get_library_units(self.work_library_name())?; + + let name = self.current_unit.primary_name(); + units + .get(&UnitKey::Secondary(name.clone(), name.clone())) + .filter(|&unit| unit.kind() == AnyKind::Secondary(SecondaryKind::PackageBody)) + } + + pub fn has_package_body(&self) -> bool { + self.get_package_body().is_some() + } + + fn get_analysis( + &self, + use_pos: Option<&SrcPos>, + unit: &'a LockedUnit, + ) -> FatalResult> { + self.make_use_of(use_pos, unit.unit_id())?; + let data = self.root.get_analysis(unit); + + // Add all referenced declaration arenas from other unit + self.arena.link(&data.result().arena); + + // Change circular dependency reference when used by another unit during analysis + // The error is changed from within the used unit into the position of the use of the unit + if data.result().has_circular_dependency { + Err(CircularDependencyError::new(use_pos)) + } else { + Ok(data) + } + } + + fn get_primary_unit(&self, library_name: &Symbol, name: &Symbol) -> Option<&'a LockedUnit> { + let units = self.root.get_library_units(library_name)?; + if let Some(unit) = units.get(&UnitKey::Primary(name.clone())) { + return Some(unit); + } + self.make_use_of_missing_unit(library_name, name, None); + None + } + + fn get_secondary_unit( + &self, + library_name: &Symbol, + primary: &Symbol, + name: &Symbol, + ) -> Option<&'a LockedUnit> { + let units = self.root.get_library_units(library_name)?; + if let Some(unit) = units.get(&UnitKey::Secondary(primary.clone(), name.clone())) { + return Some(unit); + } + self.make_use_of_missing_unit(library_name, primary, Some(name)); + None + } + + /// Given an Entity, returns a reference to the architecture denoted by `architecture_name`. + /// If this architecture cannot be found, pushes an appropriate error to the diagnostics-handler + /// and returns `EvalError::Unknown`. + /// + /// # Arguments + /// + /// * `diagnostics` Reference to the diagnostic handler + /// * `library_name` The name of the library where the entity resides + /// * `pos` The position where the architecture name was declared + /// * `entity_name` Name of the entity + /// * `architecture_name` Name of the architecture to be resolved + pub(super) fn get_architecture( + &self, + diagnostics: &mut dyn DiagnosticHandler, + library_name: &Symbol, + pos: &SrcPos, + entity_name: &Symbol, + architecture_name: &Symbol, + ) -> EvalResult> { + if let Some(unit) = self.get_secondary_unit(library_name, entity_name, architecture_name) { + let data = self.get_analysis(Some(pos), unit)?; + if let AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture(arch)) = data.deref() { + if let Some(id) = arch.ident.decl.get() { + let ent = self.arena.get(id); + if let Some(design) = DesignEnt::from_any(ent) { + return Ok(design); + } else { + bail!( + diagnostics, + Diagnostic::internal( + pos, + format!( + "Found non-design {} unit within library {}", + ent.describe(), + library_name + ) + ) + ); + } + } + } + } + + bail!( + diagnostics, + Diagnostic::new( + pos, + format!("No architecture '{architecture_name}' for entity '{library_name}.{entity_name}'"), + ErrorCode::Unresolved, + ) + ); + } + + pub fn lookup_in_library( + &self, + diagnostics: &mut dyn DiagnosticHandler, + library_name: &Symbol, + pos: &SrcPos, + primary_name: &Designator, + ) -> EvalResult> { + if let Designator::Identifier(ref primary_name) = primary_name { + if let Some(unit) = self.get_primary_unit(library_name, primary_name) { + let data = self.get_analysis(Some(pos), unit)?; + if let AnyDesignUnit::Primary(primary) = data.deref() { + if let Some(id) = primary.ent_id() { + let ent = self.arena.get(id); + if let Some(design) = DesignEnt::from_any(ent) { + return Ok(design); + } else { + bail!( + diagnostics, + Diagnostic::internal( + pos, + format!( + "Found non-design {} unit within library {}", + ent.describe(), + library_name + ), + ) + ); + } + } + } + } + } + + bail!( + diagnostics, + Diagnostic::new( + pos, + format!("No primary unit '{primary_name}' within library '{library_name}'"), + ErrorCode::Unresolved, + ) + ); + } + + // Returns None when analyzing the standard package itself + fn standard_package_region(&self) -> Option<&'a Region<'a>> { + if let Some(pkg) = self.root.standard_pkg_id.as_ref() { + self.arena.link(self.root.standard_arena.as_ref().unwrap()); + + // Ensure things that depend on the standard package are re-analyzed + self.make_use_of(None, &UnitId::package(&self.std_sym, &self.standard_sym)) + .unwrap(); + + if let AnyEntKind::Design(Design::Package(_, region)) = self.arena.get(*pkg).kind() { + Some(region) + } else { + unreachable!("Standard package is not a package"); + } + } else { + None + } + } + + pub fn source(&self) -> Source { + self.source.clone() + } + + pub fn define( + &self, + decl: &mut WithDecl, + parent: EntRef<'a>, + kind: AnyEntKind<'a>, + src_span: TokenSpan, + ) -> EntRef<'a> { + self.arena + .define(self.ctx, decl, parent, kind, src_span, Some(self.source())) + } +} diff --git a/vhdl_lang/src/analysis/assignment.rs b/vhdl_lang/src/analysis/assignment.rs new file mode 100644 index 0000000..940ade6 --- /dev/null +++ b/vhdl_lang/src/analysis/assignment.rs @@ -0,0 +1,151 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use super::analyze::*; +use super::scope::*; +use super::target::AssignmentType; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::*; +use crate::named_entity::*; + +impl<'a, 't> AnalyzeContext<'a, 't> { + // @TODO maybe make generic function for expression/waveform. + // wait until type checking to see if it makes sense + pub fn analyze_expr_assignment( + &self, + scope: &Scope<'a>, + target: &mut WithTokenSpan, + assignment_type: AssignmentType, + rhs: &mut AssignmentRightHand>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let ttyp = as_fatal(self.resolve_target(scope, target, assignment_type, diagnostics))?; + match rhs { + AssignmentRightHand::Simple(expr) => { + self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?; + } + AssignmentRightHand::Conditional(conditionals) => { + let Conditionals { + conditionals, + else_item, + } = conditionals; + for conditional in conditionals { + let Conditional { condition, item } = conditional; + self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?; + self.boolean_expr(scope, condition, diagnostics)?; + } + if let Some((expr, _)) = else_item { + self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?; + } + } + AssignmentRightHand::Selected(selection) => { + let Selection { + expression, + alternatives, + } = selection; + let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?; + for Alternative { + choices, + item, + span: _, + } in alternatives.iter_mut() + { + self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?; + self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?; + } + } + } + Ok(()) + } + + pub fn analyze_waveform_assignment( + &self, + scope: &Scope<'a>, + assignment: &mut SignalAssignment, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let ttyp = as_fatal(self.resolve_target( + scope, + &mut assignment.target, + AssignmentType::Signal, + diagnostics, + ))?; + match &mut assignment.rhs { + AssignmentRightHand::Simple(wavf) => { + self.analyze_waveform(scope, ttyp, wavf, diagnostics)?; + } + AssignmentRightHand::Conditional(conditionals) => { + let Conditionals { + conditionals, + else_item, + } = conditionals; + for conditional in conditionals { + let Conditional { condition, item } = conditional; + self.analyze_waveform(scope, ttyp, item, diagnostics)?; + self.boolean_expr(scope, condition, diagnostics)?; + } + if let Some((wavf, _)) = else_item { + self.analyze_waveform(scope, ttyp, wavf, diagnostics)?; + } + } + AssignmentRightHand::Selected(selection) => { + let Selection { + expression, + alternatives, + } = selection; + let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?; + for Alternative { + choices, + item, + span: _, + } in alternatives.iter_mut() + { + self.analyze_waveform(scope, ttyp, item, diagnostics)?; + self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?; + } + } + } + Ok(()) + } + + fn analyze_waveform( + &self, + scope: &Scope<'a>, + ttyp: Option>, + wavf: &mut Waveform, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match wavf { + Waveform::Elements(ref mut elems) => { + for elem in elems.iter_mut() { + let WaveformElement { value, after } = elem; + self.analyze_expression_for_target(scope, ttyp, value, diagnostics)?; + if let Some(expr) = after { + self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; + } + } + } + Waveform::Unaffected(_) => {} + } + Ok(()) + } + + pub fn analyze_expression_for_target( + &self, + scope: &Scope<'a>, + ttyp: Option>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + if let Some(ttyp) = ttyp { + self.expr_with_ttyp(scope, ttyp, expr, diagnostics)?; + } else { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + } + Ok(()) + } +} diff --git a/vhdl_lang/src/analysis/association.rs b/vhdl_lang/src/analysis/association.rs new file mode 100644 index 0000000..599a587 --- /dev/null +++ b/vhdl_lang/src/analysis/association.rs @@ -0,0 +1,665 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use crate::analysis::names::ObjectName; +use fnv::FnvHashMap; +use itertools::Itertools; +use vhdl_lang::TokenSpan; + +use super::analyze::*; +use super::names::ResolvedName; +use super::scope::*; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; + +#[derive(Copy, Clone)] +struct ResolvedFormal<'a> { + /// The index in the formal parameter list + idx: usize, + + /// The underlying interface object + iface: InterfaceEnt<'a>, + + // Has the formal been selected, indexed or sliced? + /// Example: + /// port map(foo.field => 0) + /// port map(foo(0) => 0) + is_partial: bool, + + /// Has the formal been converted by a function? + /// Example: + /// port map(to_slv(foo) => sig) + is_converted: bool, + + /// The type of the potentially partial or converted formal + type_mark: TypeEnt<'a>, +} + +impl<'a> ResolvedFormal<'a> { + fn new_basic(idx: usize, iface: InterfaceEnt<'a>) -> Self { + Self { + idx, + iface, + is_partial: false, + is_converted: false, + type_mark: iface.type_mark(), + } + } + + fn convert(&self, into_type: TypeEnt<'a>) -> Self { + Self { + idx: self.idx, + iface: self.iface, + is_partial: self.is_partial, + is_converted: true, + type_mark: into_type, + } + } + + fn partial_with_typ(&self, suffix_type: TypeEnt<'a>) -> Option { + if !self.is_converted { + Some(Self { + idx: self.idx, + iface: self.iface, + is_partial: true, + is_converted: self.is_converted, + type_mark: suffix_type, + }) + } else { + // Converted formals may not be further selected + None + } + } + + fn partial(&self) -> Self { + Self { + is_partial: true, + ..*self + } + } +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + fn resolve_formal( + &self, + formal_region: &FormalRegion<'a>, + scope: &Scope<'a>, + name_pos: TokenSpan, + name: &mut Name, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match name { + Name::Selected(prefix, suffix) => { + let resolved_prefix = self.resolve_formal( + formal_region, + scope, + prefix.span, + &mut prefix.item, + diagnostics, + )?; + + let suffix_ent = resolved_prefix + .type_mark + .selected(self.ctx, prefix.span, suffix) + .into_eval_result(diagnostics)?; + if let TypedSelection::RecordElement(elem) = suffix_ent { + suffix.set_unique_reference(elem.into()); + if let Some(resolved_formal) = + resolved_prefix.partial_with_typ(elem.type_mark()) + { + Ok(resolved_formal) + } else { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + } else { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + } + + Name::SelectedAll(_) => { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + Name::Designator(designator) => { + let (idx, ent) = formal_region + .lookup(&name_pos.pos(self.ctx), designator.item.designator()) + .into_eval_result(diagnostics)?; + designator.set_unique_reference(ent.inner()); + Ok(ResolvedFormal::new_basic(idx, ent)) + } + Name::Slice(ref mut prefix, ref mut drange) => { + let resolved_prefix = self.resolve_formal( + formal_region, + scope, + prefix.span, + &mut prefix.item, + diagnostics, + )?; + + if resolved_prefix.is_converted { + // Converted formals may not be further selected + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + + self.drange_unknown_type(scope, drange.as_mut(), diagnostics)?; + Ok(resolved_prefix.partial()) + } + Name::Attribute(..) => { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + Name::CallOrIndexed(ref mut fcall) => { + let prefix = if let Some(prefix) = fcall.name.item.prefix() { + prefix + } else { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + }; + + if formal_region + .lookup(&name_pos.pos(self.ctx), prefix.designator()) + .is_err() + { + // The prefix of the name was not found in the formal region + // it must be a type conversion or a single parameter function call + + let (pos, resolved_formal) = if let Some((inner_pos, inner_name)) = + to_formal_conversion_argument(&mut fcall.parameters.items) + { + ( + inner_pos, + self.resolve_formal( + formal_region, + scope, + inner_pos, + inner_name, + diagnostics, + )?, + ) + } else { + bail!( + diagnostics, + Diagnostic::invalid_formal_conversion(name_pos.pos(self.ctx)) + ); + }; + + let converted_typ = match as_fatal(self.name_resolve( + scope, + fcall.name.span, + &mut fcall.name.item, + diagnostics, + ))? { + Some(ResolvedName::Type(typ)) => { + let ctyp = resolved_formal.type_mark.base(); + if !typ.base().is_closely_related(ctyp) { + bail!( + diagnostics, + Diagnostic::invalid_type_conversion( + pos.pos(self.ctx), + ctyp, + typ + ) + ); + } + typ + } + Some(ResolvedName::Overloaded(des, overloaded)) => { + let mut candidates = Vec::with_capacity(overloaded.len()); + + for ent in overloaded.entities() { + if ent.is_function() + && ent.signature().can_be_called_with_single_parameter( + resolved_formal.type_mark, + ) + { + candidates.push(ent); + } + } + + if candidates.len() > 1 { + // Ambiguous call + bail!( + diagnostics, + Diagnostic::ambiguous_call(self.ctx, &des, candidates) + ); + } else if let Some(ent) = candidates.pop() { + fcall.name.set_unique_reference(&ent); + ent.return_type().unwrap() + } else { + // No match + bail!( + diagnostics, + Diagnostic::new( + fcall.name.pos(self.ctx), + format!( + "No function '{}' accepting {}", + fcall.name, + resolved_formal.type_mark.describe() + ), + ErrorCode::Unresolved, + ) + ); + } + } + _ => { + bail!( + diagnostics, + Diagnostic::invalid_formal_conversion(name_pos.pos(self.ctx)) + ); + } + }; + + Ok(resolved_formal.convert(converted_typ)) + } else if let Some(mut indexed_name) = fcall.as_indexed() { + let resolved_prefix = self.resolve_formal( + formal_region, + scope, + indexed_name.name.span, + &mut indexed_name.name.item, + diagnostics, + )?; + + let new_typ = self.analyze_indexed_name( + scope, + name_pos, + indexed_name.name.suffix_pos(), + resolved_prefix.type_mark, + &mut indexed_name.indexes, + diagnostics, + )?; + + if let Some(resolved_formal) = resolved_prefix.partial_with_typ(new_typ) { + Ok(resolved_formal) + } else { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + } else { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + } + Name::External(..) => { + bail!( + diagnostics, + Diagnostic::invalid_formal(name_pos.pos(self.ctx)) + ); + } + } + } + + pub fn check_positional_before_named( + &self, + elems: &[AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult { + let mut is_positional = false; + let mut fail = false; + for AssociationElement { formal, .. } in elems.iter().rev() { + if let Some(formal) = formal { + if is_positional { + fail = true; + + diagnostics.add( + formal.pos(self.ctx), + "Named arguments are not allowed before positional arguments", + ErrorCode::NamedBeforePositional, + ); + } + } else { + is_positional = true; + } + } + + if fail { + Err(EvalError::Unknown) + } else { + Ok(()) + } + } + + fn combine_formal_with_actuals<'e>( + &self, + formal_region: &FormalRegion<'a>, + scope: &Scope<'a>, + elems: &'e mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>)>> { + self.check_positional_before_named(elems, diagnostics)?; + + // Formal region index => actual position, resolved formal + let mut result = Vec::default(); + + for (actual_idx, AssociationElement { formal, actual }) in elems.iter_mut().enumerate() { + if let Some(ref mut formal) = formal { + // Named argument + let resolved_formal = as_fatal(self.resolve_formal( + formal_region, + scope, + formal.span, + &mut formal.item, + diagnostics, + ))?; + + result.push((formal.span, resolved_formal)); + } else if let Some(formal) = formal_region.nth(actual_idx) { + // Actual index is same as formal index for positional argument + let formal = ResolvedFormal::new_basic(actual_idx, formal); + result.push((actual.span, Some(formal))); + } else { + diagnostics.add( + actual.pos(self.ctx), + "Unexpected extra argument", + ErrorCode::TooManyArguments, + ); + result.push((actual.span, None)); + }; + } + Ok(result) + } + + fn check_missing_and_duplicates( + &self, + error_pos: &SrcPos, // The position of the instance/call-site + resolved_pairs: &[(TokenSpan, Option>)], + formal_region: &FormalRegion<'a>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + let mut is_error = false; + let mut result = Vec::default(); + + let mut associated: FnvHashMap)> = Default::default(); + for (actual_pos, resolved_formal) in resolved_pairs.iter() { + match resolved_formal { + Some(resolved_formal) => { + if let Some((prev_pos, prev_formal)) = associated.get(&resolved_formal.idx) { + if !(resolved_formal.is_partial && prev_formal.is_partial) { + let mut diag = Diagnostic::new( + actual_pos.pos(self.ctx), + format!( + "{} has already been associated", + resolved_formal.iface.describe(), + ), + ErrorCode::AlreadyAssociated, + ); + + diag.add_related(prev_pos.pos(self.ctx), "Previously associated here"); + is_error = true; + diagnostics.push(diag); + } + } + result.push(resolved_formal.type_mark); + associated.insert(resolved_formal.idx, (*actual_pos, *resolved_formal)); + } + None => { + is_error = true; + } + } + } + + for (idx, formal) in formal_region.iter().enumerate() { + if !(associated.contains_key(&idx) + // Default may be unconnected + || formal.has_default() + // Output ports are allowed to be unconnected + || (formal_region.typ == InterfaceType::Port && formal.is_out_or_inout_signal())) + { + let diagnostic = Diagnostic::new( + error_pos, + format!("No association of {}", formal.describe()), + ErrorCode::Unassociated, + ) + .opt_related(formal.decl_pos(), "Defined here"); + + is_error = true; + diagnostics.push(diagnostic); + } + } + + if !is_error { + Ok(result) + } else { + Err(EvalError::Unknown) + } + } + + pub fn resolve_association_formals<'e>( + &self, + error_pos: &SrcPos, // The position of the instance/call-site + formal_region: &FormalRegion<'a>, + scope: &Scope<'a>, + elems: &'e mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + let resolved_pairs = + self.combine_formal_with_actuals(formal_region, scope, elems, diagnostics)?; + self.check_missing_and_duplicates(error_pos, &resolved_pairs, formal_region, diagnostics) + } + + pub fn check_association<'e>( + &self, + error_pos: &SrcPos, // The position of the instance/call-site + formal_region: &FormalRegion<'a>, + scope: &Scope<'a>, + elems: &'e mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let resolved_pairs = + as_fatal(self.combine_formal_with_actuals(formal_region, scope, elems, diagnostics))?; + + if let Some(resolved_pairs) = resolved_pairs { + as_fatal(self.check_missing_and_duplicates( + error_pos, + &resolved_pairs, + formal_region, + diagnostics, + ))?; + + let resolved_formals = resolved_pairs + .into_iter() + .map(|(_, resolved_formal)| resolved_formal) + .collect_vec(); + + for (resolved_formal, actual) in resolved_formals + .iter() + .zip(elems.iter_mut().map(|assoc| &mut assoc.actual)) + { + match &mut actual.item { + ActualPart::Expression(expr) => { + if let Some(resolved_formal) = resolved_formal { + if formal_region.typ == InterfaceType::Parameter { + self.check_parameter_interface( + resolved_formal, + expr, + scope, + actual.span, + diagnostics, + )?; + } + self.expr_pos_with_ttyp( + scope, + resolved_formal.type_mark, + actual.span, + expr, + diagnostics, + )?; + } else { + self.expr_pos_unknown_ttyp(scope, actual.span, expr, diagnostics)?; + } + } + ActualPart::Open => {} + } + } + } + Ok(()) + } + + // LRM 4.2.2.1: In a subprogram, the interface mode must match the mode of the actual designator + // when the interface mode is signal, variable or file. Furthermore, they must be a single name. + fn check_parameter_interface( + &self, + resolved_formal: &ResolvedFormal<'a>, + expr: &mut Expression, + scope: &Scope<'a>, + actual_pos: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match resolved_formal.iface.interface_class() { + InterfaceClass::Signal => { + let Some(name) = + as_fatal(self.expression_as_name(expr, scope, actual_pos, diagnostics))? + else { + diagnostics.add( + actual_pos.pos(self.ctx), + "Expression must be a name denoting a signal", + ErrorCode::InterfaceModeMismatch, + ); + return Ok(()); + }; + if !matches!(name, ResolvedName::ObjectName( + ObjectName { base, .. }, + ) if base.class() == ObjectClass::Signal) + { + diagnostics.add( + actual_pos.pos(self.ctx), + "Name must denote a signal name", + ErrorCode::InterfaceModeMismatch, + ); + } + } + InterfaceClass::Variable => { + let Some(name) = + as_fatal(self.expression_as_name(expr, scope, actual_pos, diagnostics))? + else { + diagnostics.add( + actual_pos.pos(self.ctx), + "Expression must be a name denoting a variable or shared variable", + ErrorCode::InterfaceModeMismatch, + ); + return Ok(()); + }; + if !matches!(name, ResolvedName::ObjectName( + ObjectName { base, .. }, + ) if base.class() == ObjectClass::Variable || base.class() == ObjectClass::SharedVariable) + { + diagnostics.add( + actual_pos.pos(self.ctx), + "Name must denote a variable name", + ErrorCode::InterfaceModeMismatch, + ); + } + } + InterfaceClass::File => { + let Some(name) = + as_fatal(self.expression_as_name(expr, scope, actual_pos, diagnostics))? + else { + diagnostics.add( + actual_pos.pos(self.ctx), + "Expression must be a name denoting a file", + ErrorCode::InterfaceModeMismatch, + ); + return Ok(()); + }; + if !matches!(name, ResolvedName::Final(ent) if matches!( + ent.kind(), + AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_) + )) { + diagnostics.add( + actual_pos.pos(self.ctx), + "Name must denote a file name", + ErrorCode::InterfaceModeMismatch, + ); + } + } + _ => {} + } + Ok(()) + } + + fn expression_as_name( + &self, + expr: &mut Expression, + scope: &Scope<'a>, + span: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match expr { + Expression::Name(name) => { + let resolved = self.name_resolve(scope, span, name, diagnostics)?; + Ok(resolved) + } + _ => Err(EvalError::Unknown), + } + } +} + +fn to_formal_conversion_argument( + parameters: &mut [AssociationElement], +) -> Option<(TokenSpan, &mut Box)> { + if let &mut [AssociationElement { + ref formal, + ref mut actual, + }] = parameters + { + if formal.is_some() { + return None; + } else if let ActualPart::Expression(Expression::Name(ref mut actual_name)) = actual.item { + return Some((actual.span, actual_name)); + } + } + None +} + +impl Diagnostic { + pub fn invalid_formal(pos: impl AsRef) -> Diagnostic { + Diagnostic::new(pos, "Invalid formal", ErrorCode::InvalidFormal) + } + + pub fn invalid_formal_conversion(pos: impl AsRef) -> Diagnostic { + Diagnostic::new( + pos, + "Invalid formal conversion", + ErrorCode::InvalidFormalConversion, + ) + } + + pub fn invalid_type_conversion( + pos: impl AsRef, + from: BaseType<'_>, + to: TypeEnt<'_>, + ) -> Diagnostic { + Diagnostic::new( + pos, + format!( + "{} cannot be converted to {}", + from.describe(), + to.describe() + ), + ErrorCode::TypeMismatch, + ) + } +} diff --git a/vhdl_lang/src/analysis/concurrent.rs b/vhdl_lang/src/analysis/concurrent.rs new file mode 100644 index 0000000..f1f684d --- /dev/null +++ b/vhdl_lang/src/analysis/concurrent.rs @@ -0,0 +1,483 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::analysis::names::ResolvedName; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use crate::{HasTokenSpan, TokenSpan}; +use analyze::*; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn analyze_concurrent_part( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + statements: &mut [LabeledConcurrentStatement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for statement in statements.iter_mut() { + let parent = if let Some(id) = statement.label.decl.get() { + self.arena.get(id) + } else { + parent + }; + + self.analyze_concurrent_statement(scope, parent, statement, diagnostics)?; + } + + Ok(()) + } + + pub fn define_labels_for_concurrent_part( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + statements: &mut [LabeledConcurrentStatement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for statement in statements.iter_mut() { + let span = statement.span(); + if let Some(ref mut label) = statement.label.tree { + let ent = self.arena.explicit( + label.name(), + parent, + AnyEntKind::Concurrent(statement.statement.item.label_typ()), + Some(label.pos(self.ctx)), + span, + Some(self.source()), + ); + statement.label.decl.set(ent.id()); + scope.add(ent, diagnostics); + } else if statement.statement.item.can_have_label() { + // Generate an anonymous label if it is not explicitly defined + let ent = self.arena.alloc( + scope.anonymous_designator(), + Some(parent), + Related::None, + AnyEntKind::Concurrent(statement.statement.item.label_typ()), + None, + span, + Some(self.source()), + ); + statement.label.decl.set(ent.id()); + } + } + + Ok(()) + } + + fn analyze_concurrent_statement( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + statement: &mut LabeledConcurrentStatement, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let src_span = statement.span(); + match statement.statement.item { + ConcurrentStatement::Block(ref mut block) => { + if let Some(ref mut guard_condition) = block.guard_condition { + self.boolean_expr(scope, guard_condition, diagnostics)?; + } + let nested = scope.nested(); + if let Some(ref mut list) = block.header.generic_clause { + self.analyze_interface_list(&nested, parent, list, diagnostics)?; + } + if let Some(ref mut list) = block.header.generic_map { + self.analyze_assoc_elems(scope, &mut list.list.items[..], diagnostics)?; + } + if let Some(ref mut list) = block.header.port_clause { + self.analyze_interface_list(&nested, parent, list, diagnostics)?; + } + if let Some(ref mut list) = block.header.port_map { + self.analyze_assoc_elems(scope, &mut list.list.items[..], diagnostics)?; + } + + self.define_labels_for_concurrent_part( + &nested, + parent, + &mut block.statements, + diagnostics, + )?; + self.analyze_declarative_part(&nested, parent, &mut block.decl, diagnostics)?; + self.analyze_concurrent_part(&nested, parent, &mut block.statements, diagnostics)?; + } + ConcurrentStatement::Process(ref mut process) => { + let ProcessStatement { + postponed: _, + sensitivity_list, + decl, + statements, + end_label_pos: _, + .. + } = process; + if let Some(sensitivity_list) = sensitivity_list { + match &mut sensitivity_list.item { + SensitivityList::Names(names) => { + self.sensitivity_list_check(scope, names, diagnostics)?; + } + SensitivityList::All => {} + } + } + let nested = scope.nested(); + self.define_labels_for_sequential_part(&nested, parent, statements, diagnostics)?; + self.analyze_declarative_part(&nested, parent, decl, diagnostics)?; + self.analyze_sequential_part(&nested, parent, statements, diagnostics)?; + } + ConcurrentStatement::ForGenerate(ref mut gen) => { + let ForGenerateStatement { + index_name, + discrete_range, + body, + end_label_pos: _, + .. + } = gen; + let typ = as_fatal(self.drange_type(scope, discrete_range, diagnostics))?; + let nested = scope.nested(); + nested.add( + index_name.define( + self.ctx, + self.arena, + parent, + AnyEntKind::LoopParameter(typ), + src_span, + Some(self.source()), + ), + diagnostics, + ); + self.analyze_generate_body(&nested, parent, body, src_span, diagnostics)?; + } + ConcurrentStatement::IfGenerate(ref mut gen) => { + let Conditionals { + conditionals, + else_item, + } = &mut gen.conds; + for conditional in conditionals.iter_mut() { + let Conditional { condition, item } = conditional; + self.boolean_expr(scope, condition, diagnostics)?; + let nested = scope.nested(); + self.analyze_generate_body(&nested, parent, item, src_span, diagnostics)?; + } + if let Some((ref mut else_item, _)) = else_item { + let nested = scope.nested(); + self.analyze_generate_body(&nested, parent, else_item, src_span, diagnostics)?; + } + } + ConcurrentStatement::CaseGenerate(ref mut gen) => { + let CaseGenerateStatement { + sels: + Selection { + ref mut expression, + ref mut alternatives, + }, + end_label_pos: _, + .. + } = gen; + + let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?; + for alternative in alternatives.iter_mut() { + let Alternative { + ref mut choices, + ref mut item, + span: _, + } = alternative; + self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?; + let nested = scope.nested(); + self.analyze_generate_body(&nested, parent, item, src_span, diagnostics)?; + } + } + ConcurrentStatement::Instance(ref mut instance) => { + self.analyze_instance(scope, instance, diagnostics)?; + } + ConcurrentStatement::Assignment(ref mut assign) => { + // @TODO more delaymechanism + let ConcurrentSignalAssignment { assignment, .. } = assign; + self.analyze_waveform_assignment(scope, assignment, diagnostics)?; + } + ConcurrentStatement::ProcedureCall(ref mut pcall) => { + let ConcurrentProcedureCall { call, .. } = pcall; + self.analyze_procedure_call(scope, call, diagnostics)?; + } + ConcurrentStatement::Assert(ref mut assert) => { + let ConcurrentAssertStatement { + postponed: _postponed, + statement: + AssertStatement { + condition, + report, + severity, + }, + } = assert; + self.boolean_expr(scope, condition, diagnostics)?; + if let Some(expr) = report { + self.expr_with_ttyp(scope, self.string(), expr, diagnostics)?; + } + if let Some(expr) = severity { + self.expr_with_ttyp(scope, self.severity_level(), expr, diagnostics)?; + } + } + }; + Ok(()) + } + + fn analyze_generate_body( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + body: &mut GenerateBody, + span: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let GenerateBody { + alternative_label, + decl, + statements, + .. + } = body; + + let mut inner_parent = parent; + if let Some(label) = alternative_label { + let ent = label.define( + self.ctx, + self.arena, + parent, + AnyEntKind::Concurrent(Some(Concurrent::Generate)), + span, + Some(self.source()), + ); + scope.add(ent, diagnostics); + inner_parent = ent; + } + + // Pre-declare labels + self.define_labels_for_concurrent_part(scope, parent, statements, diagnostics)?; + + if let Some((ref mut decl, _)) = decl { + self.analyze_declarative_part(scope, parent, decl, diagnostics)?; + } + self.analyze_concurrent_part(scope, inner_parent, statements, diagnostics)?; + + Ok(()) + } + + fn analyze_instance( + &self, + scope: &Scope<'a>, + instance: &mut InstantiationStatement, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match instance.unit { + InstantiatedUnit::Entity(ref mut entity_name, ref mut architecture_name) => { + let Some(resolved) = as_fatal(self.name_resolve( + scope, + entity_name.span, + &mut entity_name.item, + diagnostics, + ))? + else { + return Ok(()); + }; + match resolved { + ResolvedName::Design(ent) => match ent.kind() { + Design::Entity(_, ent_region) => { + if let Designator::Identifier(entity_ident) = ent.designator() { + if let Some(library_name) = ent.library_name() { + if let Some(ref mut architecture_name) = architecture_name { + if let Some(arch) = as_fatal(self.get_architecture( + diagnostics, + library_name, + self.ctx.get_pos(architecture_name.item.token), + entity_ident, + &architecture_name.item.item, + ))? { + architecture_name.set_unique_reference(&arch); + } + } + } + } + + let (generic_region, port_region) = ent_region.to_entity_formal(); + + self.check_association( + &entity_name.pos(self.ctx), + &generic_region, + scope, + instance + .generic_map + .as_mut() + .map(|it| it.list.items.as_mut_slice()) + .unwrap_or(&mut []), + diagnostics, + )?; + self.check_association( + &entity_name.pos(self.ctx), + &port_region, + scope, + instance + .port_map + .as_mut() + .map(|it| it.list.items.as_mut_slice()) + .unwrap_or(&mut []), + diagnostics, + )?; + Ok(()) + } + _ => { + diagnostics.push( + resolved + .kind_error(entity_name.suffix_pos().pos(self.ctx), "entity"), + ); + Ok(()) + } + }, + other => { + diagnostics.push( + other.kind_error(entity_name.suffix_pos().pos(self.ctx), "entity"), + ); + Ok(()) + } + } + } + InstantiatedUnit::Component(ref mut component_name) => { + let Some(resolved) = as_fatal(self.name_resolve( + scope, + component_name.span, + &mut component_name.item, + diagnostics, + ))? + else { + return Ok(()); + }; + + let ent = match resolved { + ResolvedName::Final(ent) => ent, + other => { + diagnostics.push( + other + .kind_error(component_name.suffix_pos().pos(self.ctx), "component"), + ); + return Ok(()); + } + }; + + if let AnyEntKind::Component(ent_region) = ent.kind() { + let (generic_region, port_region) = ent_region.to_entity_formal(); + self.check_association( + &component_name.pos(self.ctx), + &generic_region, + scope, + instance + .generic_map + .as_mut() + .map(|it| it.list.items.as_mut_slice()) + .unwrap_or(&mut []), + diagnostics, + )?; + self.check_association( + &component_name.pos(self.ctx), + &port_region, + scope, + instance + .port_map + .as_mut() + .map(|it| it.list.items.as_mut_slice()) + .unwrap_or(&mut []), + diagnostics, + )?; + Ok(()) + } else { + diagnostics.push( + resolved.kind_error(component_name.suffix_pos().pos(self.ctx), "component"), + ); + Ok(()) + } + } + InstantiatedUnit::Configuration(ref mut config_name) => { + let Some(resolved) = as_fatal(self.name_resolve( + scope, + config_name.span, + &mut config_name.item, + diagnostics, + ))? + else { + return Ok(()); + }; + match resolved { + ResolvedName::Design(ent) if matches!(ent.kind(), Design::Configuration) => {} + other => { + diagnostics.push( + other.kind_error( + config_name.suffix_pos().pos(self.ctx), + "configuration", + ), + ); + return Ok(()); + } + } + + self.analyze_map_aspect(scope, &mut instance.generic_map, diagnostics)?; + self.analyze_map_aspect(scope, &mut instance.port_map, diagnostics) + } + } + } + + pub fn analyze_map_aspect( + &self, + scope: &Scope<'a>, + map: &mut Option, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let Some(aspect) = map else { + return Ok(()); + }; + self.analyze_assoc_elems(scope, aspect.list.items.as_mut_slice(), diagnostics) + } + + pub fn sensitivity_list_check( + &self, + scope: &Scope<'a>, + names: &mut [WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for name in names.iter_mut() { + if let Some(object_name) = as_fatal(self.resolve_object_name( + scope, + name.span, + &mut name.item, + "is not a signal and cannot be in a sensitivity list", + ErrorCode::DisallowedInSensitivityList, + diagnostics, + ))? { + if object_name.base.class() != ObjectClass::Signal { + diagnostics.add( + name.pos(self.ctx), + format!( + "{} is not a signal and cannot be in a sensitivity list", + object_name.base.describe_class() + ), + ErrorCode::DisallowedInSensitivityList, + ) + } else if object_name.base.mode() == Some(&InterfaceMode::Simple(Mode::Out)) + && !object_name.base.is_port() + { + diagnostics.add( + name.pos(self.ctx), + format!( + "{} cannot be in a sensitivity list", + object_name.base.describe_class() + ), + ErrorCode::DisallowedInSensitivityList, + ) + } + } + } + Ok(()) + } +} diff --git a/vhdl_lang/src/analysis/declarative.rs b/vhdl_lang/src/analysis/declarative.rs new file mode 100644 index 0000000..c6f8288 --- /dev/null +++ b/vhdl_lang/src/analysis/declarative.rs @@ -0,0 +1,1325 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::names::*; +use super::*; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::{Signature, *}; +use crate::{ast, named_entity, HasTokenSpan}; +use analyze::*; +use fnv::FnvHashMap; +use itertools::Itertools; +use std::collections::hash_map::Entry; +use std::collections::HashSet; +use vhdl_lang::TokenSpan; + +impl Declaration { + /// Returns whether the declaration denoted by `self` is allowed in the given context. + /// For example, within an architecture, only constants, signals and shared variables are allowed, + /// variables are not. + /// + /// ### Conforming example: + /// ```vhdl + /// architecture arch of ent is + /// signal foo : bit; + /// begin + /// end arch; + /// ``` + /// + /// ### Non-Conforming example: + /// ```vhdl + /// architecture arch of ent is + /// variable foo : bit; + /// begin + /// end arch; + /// ``` + /// + /// The context is given by the parent element of the declaration. + pub fn is_allowed_in_context(&self, parent: &AnyEntKind<'_>) -> bool { + use Declaration::*; + use ObjectClass::*; + match parent { + // LRM: block_declarative_item + AnyEntKind::Design(Design::Architecture(..)) + | AnyEntKind::Concurrent(Some(Concurrent::Block | Concurrent::Generate)) => matches!( + self, + Object(ObjectDeclaration { + class: Constant | Signal | SharedVariable, + .. + }) | File(_) + | Type(_) + | Component(_) + | Attribute(_) + | Alias(_) + | SubprogramDeclaration(_) + | SubprogramInstantiation(_) + | SubprogramBody(_) + | Use(_) + | Package(_) + | Configuration(_) + | View(_) + ), + // LRM: configuration_declarative_item + AnyEntKind::Design(Design::Configuration) => { + matches!(self, Use(_) | Attribute(ast::Attribute::Specification(_))) + } + // LRM: entity_declarative_item + AnyEntKind::Design(Design::Entity(..)) => matches!( + self, + Object(_) + | File(_) + | Type(_) + | Attribute(_) + | Alias(_) + | SubprogramDeclaration(_) + | SubprogramInstantiation(_) + | SubprogramBody(_) + | Use(_) + | Package(_) + | View(_) + ), + // LRM: package_body_declarative_item + AnyEntKind::Design(Design::PackageBody(..) | Design::UninstPackage(..)) + | AnyEntKind::Overloaded( + Overloaded::SubprogramDecl(_) + | Overloaded::Subprogram(_) + | Overloaded::UninstSubprogramDecl(..) + | Overloaded::UninstSubprogram(..), + ) + | AnyEntKind::Concurrent(Some(Concurrent::Process)) + | AnyEntKind::Type(named_entity::Type::Protected(..)) => matches!( + self, + Object(ObjectDeclaration { + class: Constant | Variable | SharedVariable, + .. + }) | File(_) + | Type(_) + | Attribute(_) + | Alias(_) + | SubprogramDeclaration(_) + | SubprogramInstantiation(_) + | SubprogramBody(_) + | Use(_) + | Package(_) + ), + // LRM: package_declarative_item + AnyEntKind::Design(Design::Package(..)) => matches!( + self, + Object(_) + | File(_) + | Type(_) + | Component(_) + | Attribute(_) + | Alias(_) + | SubprogramDeclaration(_) + | SubprogramInstantiation(_) + | Use(_) + | Package(_) + | View(_) + ), + _ => { + // AnyEntKind::Library is used in tests for a generic declarative region + if !(cfg!(test) && matches!(parent, AnyEntKind::Library)) { + debug_assert!(false, "Parent should be a declarative region"); + } + true + } + } + } +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn analyze_declarative_part( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + declarations: &mut [WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let mut incomplete_types: FnvHashMap, SrcPos)> = FnvHashMap::default(); + + for i in 0..declarations.len() { + let (WithTokenSpan { item: decl, span }, remaining) = + declarations[i..].split_first_mut().unwrap(); + + if !decl.is_allowed_in_context(parent.kind()) { + diagnostics.add( + span.pos(self.ctx), + format!("{} declaration not allowed here", decl.describe()), + ErrorCode::DeclarationNotAllowed, + ) + } + + // Handle incomplete types + match decl { + Declaration::Type(type_decl) => match type_decl.def { + TypeDefinition::Incomplete(ref mut reference) => { + match incomplete_types.entry(type_decl.ident.name().clone()) { + Entry::Vacant(entry) => { + let full_definiton = + find_full_type_definition(type_decl.ident.name(), remaining); + + let (decl_pos, span) = match full_definiton { + Some(full_decl) => ( + self.ctx.get_pos(full_decl.ident.tree.token), + full_decl.span, + ), + None => { + let error = Diagnostic::new( + type_decl.ident.pos(self.ctx), + format!( + "Missing full type declaration of incomplete type '{}'", + type_decl.ident.name() + ), + ErrorCode::MissingFullTypeDeclaration, + ).related(type_decl.ident.pos(self.ctx), "The full type declaration shall occur immediately within the same declarative part"); + diagnostics.push(error); + (type_decl.ident.pos(self.ctx), type_decl.span) + } + }; + + let designator = + Designator::Identifier(type_decl.ident.name().clone()); + + // Set incomplete type defintion to position of full declaration + let ent = self.arena.explicit( + designator, + parent, + AnyEntKind::Type(Type::Incomplete), + Some(decl_pos), + span, + Some(self.source()), + ); + reference.set_unique_reference(ent); + + entry.insert((ent, type_decl.ident.pos(self.ctx).clone())); + scope.add(ent, diagnostics); + } + Entry::Occupied(entry) => { + let (_, decl_pos) = entry.get(); + + diagnostics.push(Diagnostic::duplicate_error( + &type_decl.ident, + type_decl.ident.pos(self.ctx), + Some(decl_pos), + )); + } + } + } + _ => { + let incomplete_type = incomplete_types.get(type_decl.ident.name()); + if let Some((incomplete_type, _)) = incomplete_type { + self.analyze_type_declaration( + scope, + parent, + type_decl, + Some(incomplete_type.id()), + diagnostics, + )?; + } else { + self.analyze_type_declaration( + scope, + parent, + type_decl, + None, + diagnostics, + )?; + } + } + }, + _ => { + self.analyze_declaration(scope, parent, &mut declarations[i], diagnostics)?; + } + } + } + Ok(()) + } + + fn analyze_alias_declaration( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + alias: &mut AliasDeclaration, + src_span: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let AliasDeclaration { + designator, + name, + subtype_indication, + signature, + is_token: _, + } = alias; + + let resolved_name = self.name_resolve(scope, name.span, &mut name.item, diagnostics); + + if let Some(ref mut subtype_indication) = subtype_indication { + // Object alias + self.analyze_subtype_indication(scope, subtype_indication, diagnostics)?; + } + + let resolved_name = resolved_name?; + + let kind = { + match resolved_name { + ResolvedName::ObjectName(oname) => { + if let Some(ref signature) = signature { + diagnostics.push(Diagnostic::should_not_have_signature( + "Alias", + signature.pos(self.ctx), + )); + } + match oname.base { + ObjectBase::Object(base_object) => AnyEntKind::ObjectAlias { + base_object, + type_mark: oname.type_mark(), + }, + ObjectBase::ObjectAlias(base_object, _) => AnyEntKind::ObjectAlias { + base_object, + type_mark: oname.type_mark(), + }, + ObjectBase::ExternalName(class) => AnyEntKind::ExternalAlias { + class, + type_mark: oname.type_mark(), + }, + ObjectBase::DeferredConstant(_) => { + // @TODO handle + return Err(EvalError::Unknown); + } + } + } + ResolvedName::Library(_) + | ResolvedName::Design(_) + | ResolvedName::Expression(_) => { + if let Some(ref signature) = signature { + diagnostics.push(Diagnostic::should_not_have_signature( + "Alias", + signature.pos(self.ctx), + )); + } + diagnostics.add( + name.pos(self.ctx), + format!("{} cannot be aliased", resolved_name.describe_type()), + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + ResolvedName::Type(typ) => { + if let Some(ref signature) = signature { + diagnostics.push(Diagnostic::should_not_have_signature( + "Alias", + signature.pos(self.ctx), + )); + } + AnyEntKind::Type(Type::Alias(typ)) + } + ResolvedName::Overloaded(des, overloaded) => { + if let Some(ref mut signature) = signature { + // TODO: Uninstantiated subprogram in aliases + let signature_key = + self.resolve_signature(scope, signature, diagnostics)?; + if let Some(ent) = overloaded.get(&SubprogramKey::Normal(signature_key)) { + if let Some(reference) = name.item.suffix_reference_mut() { + reference.set_unique_reference(&ent); + } + AnyEntKind::Overloaded(Overloaded::Alias(ent)) + } else { + diagnostics.push(Diagnostic::no_overloaded_with_signature( + des.pos(self.ctx), + &des.item, + &overloaded, + )); + return Err(EvalError::Unknown); + } + } else { + diagnostics.push(Diagnostic::signature_required(name.pos(self.ctx))); + return Err(EvalError::Unknown); + } + } + ResolvedName::Final(ent) => { + if let Some(ent) = ViewEnt::from_any(ent) { + AnyEntKind::View(*ent.subtype()) + } else { + // @TODO some of these can probably be aliased + return Err(EvalError::Unknown); + } + } + } + }; + + Ok(designator.define( + self.ctx, + self.arena, + parent, + kind, + src_span, + Some(self.source()), + )) + } + + pub(crate) fn analyze_declaration( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + decl: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let src_span = decl.span(); + match &mut decl.item { + Declaration::Alias(alias) => { + if let Some(ent) = as_fatal(self.analyze_alias_declaration( + scope, + parent, + alias, + decl.span, + diagnostics, + ))? { + scope.add(ent, diagnostics); + + for implicit in ent.as_actual().implicits.iter() { + match OverloadedEnt::from_any(implicit) { + Some(implicit) => { + let impicit_alias = self.arena.implicit( + ent, + implicit.designator().clone(), + AnyEntKind::Overloaded(Overloaded::Alias(implicit)), + ); + scope.add(impicit_alias, diagnostics); + } + None => { + eprintln!( + "Expect implicit declaration to be overloaded, got: {}", + implicit.describe() + ) + } + } + } + } + } + Declaration::Object(ref mut object_decl) => { + let subtype = self.resolve_subtype_indication( + scope, + &mut object_decl.subtype_indication, + diagnostics, + ); + + if let Some(ref mut expr) = object_decl.expression { + if let Ok(ref subtype) = subtype { + self.expr_pos_with_ttyp( + scope, + subtype.type_mark(), + expr.span, + &mut expr.item, + diagnostics, + )?; + } else { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + } + } + + if let Some(subtype) = as_fatal(subtype)? { + for ident in &mut object_decl.idents { + let kind = if object_decl.class == ObjectClass::Constant + && object_decl.expression.is_none() + { + AnyEntKind::DeferredConstant(subtype) + } else { + AnyEntKind::Object(Object { + class: object_decl.class, + iface: None, + has_default: object_decl.expression.is_some(), + subtype, + }) + }; + let declared_by = if object_decl.class == ObjectClass::Constant + && object_decl.expression.is_some() + { + self.find_deferred_constant_declaration(scope, &ident.tree.item) + } else { + None + }; + let object_ent = self.arena.alloc( + ident.tree.item.clone().into(), + Some(parent), + if let Some(declared_by) = declared_by { + Related::DeclaredBy(declared_by) + } else { + Related::None + }, + kind, + Some(ident.pos(self.ctx).clone()), + src_span, + Some(self.source()), + ); + ident.decl.set(object_ent.id()); + + scope.add(object_ent, diagnostics); + } + } + } + Declaration::File(ref mut file) => { + let FileDeclaration { + idents, + colon_token: _, + subtype_indication, + open_info, + file_name, + } = file; + + let subtype = as_fatal(self.resolve_subtype_indication( + scope, + subtype_indication, + diagnostics, + ))?; + + if let Some((_, ref mut expr)) = open_info { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + } + if let Some((_, ref mut expr)) = file_name { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + } + + if let Some(subtype) = subtype { + for ident in idents { + scope.add( + self.define(ident, parent, AnyEntKind::File(subtype), src_span), + diagnostics, + ); + } + } + } + Declaration::Component(ref mut component) => { + let nested = scope.nested(); + let ent = self.define( + &mut component.ident, + parent, + AnyEntKind::Component(Region::default()), + src_span, + ); + if let Some(generic_list) = &mut component.generic_list { + self.analyze_interface_list(&nested, ent, generic_list, diagnostics)?; + } + if let Some(port_list) = &mut component.port_list { + self.analyze_interface_list(&nested, ent, port_list, diagnostics)?; + } + + let kind = AnyEntKind::Component(nested.into_region()); + unsafe { + ent.set_kind(kind); + } + + scope.add(ent, diagnostics); + } + Declaration::Attribute(ref mut attr) => match attr { + Attribute::Declaration(ref mut attr_decl) => { + if let Some(typ) = as_fatal(self.type_name( + scope, + attr_decl.type_mark.span, + &mut attr_decl.type_mark.item, + diagnostics, + ))? { + scope.add( + self.define( + &mut attr_decl.ident, + parent, + AnyEntKind::Attribute(typ), + src_span, + ), + diagnostics, + ); + } + } + Attribute::Specification(ref mut attr_spec) => { + self.attribute_specification(scope, parent, attr_spec, diagnostics)?; + } + }, + Declaration::SubprogramBody(ref mut body) => { + return self.subprogram_body(scope, parent, body, diagnostics); + } + Declaration::SubprogramDeclaration(ref mut subdecl) => { + match as_fatal(self.subprogram_specification( + scope, + parent, + &mut subdecl.specification, + subdecl.span, + Overloaded::SubprogramDecl, + diagnostics, + ))? { + Some((_, ent)) => { + scope.add(ent, diagnostics); + } + None => { + return Ok(()); + } + } + } + Declaration::SubprogramInstantiation(ref mut instance) => { + let subpgm_ent = self.define( + &mut instance.ident, + parent, + AnyEntKind::Overloaded(Overloaded::Subprogram(Signature::new( + FormalRegion::new_params(), + None, + ))), + src_span, + ); + let referenced_name = &mut instance.subprogram_name; + if let Some(name) = as_fatal(self.name_resolve( + scope, + referenced_name.span, + &mut referenced_name.item, + diagnostics, + ))? { + if let Some(signature) = as_fatal(self.generic_subprogram_instance( + scope, + &subpgm_ent, + &name, + instance, + diagnostics, + ))? { + unsafe { + subpgm_ent + .set_kind(AnyEntKind::Overloaded(Overloaded::Subprogram(signature))) + } + scope.add(subpgm_ent, diagnostics) + } + } + } + Declaration::Use(ref mut use_clause) => { + self.analyze_use_clause(scope, use_clause, diagnostics)?; + } + Declaration::Package(ref mut instance) => { + let ent = self.define( + &mut instance.ident, + parent, + AnyEntKind::Design(Design::PackageInstance(Region::default())), + src_span, + ); + + if let Some(pkg_region) = + as_fatal(self.generic_package_instance(scope, ent, instance, diagnostics))? + { + let kind = AnyEntKind::Design(Design::PackageInstance(pkg_region)); + unsafe { + ent.set_kind(kind); + } + scope.add(ent, diagnostics); + } + } + Declaration::Configuration(..) => {} + Declaration::View(view) => { + if let Some(view) = as_fatal(self.analyze_view_declaration( + scope, + parent, + view, + decl.span, + diagnostics, + ))? { + scope.add(view, diagnostics); + } + } + Declaration::Type(..) => unreachable!("Handled elsewhere"), + }; + + Ok(()) + } + + /// Analyzes a mode view declaration. + /// * Checks that the type of the view declaration is a record type + /// * Checks that all elements are associated in the view + fn analyze_view_declaration( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + view: &mut ModeViewDeclaration, + src_span: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let typ = self.resolve_subtype_indication(scope, &mut view.typ, diagnostics)?; + let record_region = match typ.type_mark().kind() { + Type::Record(region) => region, + _ => { + let diag = Diagnostic::new( + view.typ.type_mark.pos(self.ctx), + format!( + "The type of a view must be a record type, not {}", + typ.type_mark().describe() + ), + ErrorCode::TypeMismatch, + ) + .opt_related( + typ.type_mark().decl_pos.as_ref(), + format!("{} declared here", typ.type_mark().describe()), + ); + bail!(diagnostics, diag); + } + }; + let mut unassociated: HashSet<_> = record_region.elems.iter().collect(); + for element in view.elements.iter_mut() { + for name in element.names.iter_mut() { + let desi = Designator::Identifier(name.tree.item.clone()); + let Some(record_element) = record_region.lookup(&desi) else { + diagnostics.push(Diagnostic::new( + name.pos(self.ctx), + format!("Not a part of {}", typ.type_mark().describe()), + ErrorCode::Unresolved, + )); + continue; + }; + name.decl.set_unique_reference(&record_element); + unassociated.remove(&record_element); + } + } + if !unassociated.is_empty() { + diagnostics.add( + view.ident.pos(self.ctx), + pretty_format_unassociated_message(&unassociated), + ErrorCode::Unassociated, + ); + } + Ok(self.define(&mut view.ident, parent, AnyEntKind::View(typ), src_span)) + } + + fn find_deferred_constant_declaration( + &self, + scope: &Scope<'a>, + ident: &Symbol, + ) -> Option> { + if let Some(NamedEntities::Single(ent)) = scope.lookup_immediate(&ident.into()) { + if ent.kind().is_deferred_constant() { + return Some(ent); + } + } + None + } + + fn attribute_specification( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + attr_spec: &mut AttributeSpecification, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let AttributeSpecification { + ident, + entity_name, + entity_class, + expr, + colon_token: _, + } = attr_spec; + + let attr_ent = match scope.lookup(&Designator::Identifier(ident.item.name().clone())) { + Ok(NamedEntities::Single(ent)) => { + ident.set_unique_reference(ent); + if let Some(attr_ent) = AttributeEnt::from_any(ent) { + self.expr_pos_with_ttyp( + scope, + attr_ent.typ(), + expr.span, + &mut expr.item, + diagnostics, + )?; + attr_ent + } else { + diagnostics.add( + ident.item.pos(self.ctx), + format!("{} is not an attribute", ent.describe()), + ErrorCode::MismatchedKinds, + ); + return Ok(()); + } + } + Ok(NamedEntities::Overloaded(_)) => { + diagnostics.add( + ident.item.pos(self.ctx), + format!("Overloaded name '{}' is not an attribute", ident.item), + ErrorCode::MismatchedKinds, + ); + return Ok(()); + } + Err(err) => { + diagnostics.push(err.into_diagnostic(self.ctx, ident.item.token)); + return Ok(()); + } + }; + + if let EntityName::Name(EntityTag { + designator, + signature, + }) = entity_name + { + let ent: EntRef<'_> = match scope.lookup(&designator.item.item) { + Ok(NamedEntities::Single(ent)) => { + designator.set_unique_reference(ent); + + if let Some(signature) = signature { + diagnostics.push(Diagnostic::should_not_have_signature( + "Attribute specification", + signature.pos(self.ctx), + )); + } + ent + } + Ok(NamedEntities::Overloaded(overloaded)) => { + if let Some(signature) = signature { + match as_fatal(self.resolve_signature(scope, signature, diagnostics))? { + Some(signature_key) => { + if let Some(ent) = + overloaded.get(&SubprogramKey::Normal(signature_key)) + { + designator.set_unique_reference(&ent); + ent.into() + } else { + diagnostics.push(Diagnostic::no_overloaded_with_signature( + designator.pos(self.ctx), + &designator.item.item, + &overloaded, + )); + return Ok(()); + } + } + None => { + return Ok(()); + } + } + } else if let Some(ent) = overloaded.as_unique() { + designator.set_unique_reference(ent); + ent + } else { + diagnostics.push(Diagnostic::signature_required(designator.pos(self.ctx))); + return Ok(()); + } + } + Err(err) => { + diagnostics.push(err.into_diagnostic(self.ctx, designator.token)); + return Ok(()); + } + }; + + // Attributes affect the underlying entity and cannot be set directly on aliases + let ent = ent.as_actual(); + + if Some(*entity_class) != get_entity_class(ent) { + diagnostics.add( + designator.pos(self.ctx), + format!("{} is not of class {}", ent.describe(), entity_class), + ErrorCode::MismatchedEntityClass, + ); + return Ok(()); + } + + match entity_class { + EntityClass::Architecture + | EntityClass::Entity + | EntityClass::Package + | EntityClass::Configuration => { + if ent != parent { + diagnostics.add( + designator.pos(self.ctx), + "Attribute specification must be in the immediate declarative part", + ErrorCode::MisplacedAttributeSpec, + ); + return Ok(()); + } + } + EntityClass::Signal + | EntityClass::Variable + | EntityClass::Procedure + | EntityClass::Function + | EntityClass::Component + | EntityClass::Constant + | EntityClass::Type + | EntityClass::Subtype + | EntityClass::Literal + | EntityClass::Units + | EntityClass::File + | EntityClass::Label => { + if ent.parent != Some(parent) { + diagnostics.add( + designator.pos(self.ctx), + "Attribute specification must be in the immediate declarative part", + ErrorCode::MisplacedAttributeSpec, + ); + return Ok(()); + } + } + } + + let res = unsafe { + self.arena + .add_attr(ent.id(), designator.pos(self.ctx), attr_ent) + }; + + if let Err(diagnostic) = res { + diagnostics.push(diagnostic); + } + } + + Ok(()) + } + + pub fn analyze_interface_declaration( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + decl: &mut InterfaceDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + let span = decl.span(); + let ent = match decl { + InterfaceDeclaration::File(ref mut file_decl) => { + let file_type = self.resolve_subtype_indication( + scope, + &mut file_decl.subtype_indication, + diagnostics, + )?; + file_decl + .idents + .iter_mut() + .map(|ident| { + self.define( + ident, + parent, + AnyEntKind::InterfaceFile(file_type.type_mark().to_owned()), + span, + ) + }) + .collect() + } + InterfaceDeclaration::Object(ref mut object_decl) => { + self.analyze_interface_object_declaration(scope, parent, object_decl, diagnostics)? + } + InterfaceDeclaration::Type(ref mut ident) => { + let typ = TypeEnt::from_any(self.define( + ident, + parent, + AnyEntKind::Type(Type::Interface), + span, + )) + .unwrap(); + + let implicit = [ + self.comparison(Operator::EQ, typ), + self.comparison(Operator::NE, typ), + ]; + + for ent in implicit { + unsafe { + self.arena.add_implicit(typ.id(), ent); + } + + scope.add(ent, diagnostics); + } + + vec![typ.into()] + } + InterfaceDeclaration::Subprogram(ref mut subpgm) => { + let (_, ent) = self.subprogram_specification( + scope, + parent, + &mut subpgm.specification, + subpgm.span, + Overloaded::InterfaceSubprogram, + diagnostics, + )?; + vec![ent] + } + InterfaceDeclaration::Package(ref mut instance) => { + let package_region = self.analyze_package_instance_name( + scope, + &mut instance.package_name, + diagnostics, + )?; + + vec![self.define( + &mut instance.ident, + parent, + AnyEntKind::Design(Design::InterfacePackageInstance(package_region)), + span, + )] + } + }; + Ok(ent) + } + + pub fn analyze_interface_object_declaration( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + object_decl: &mut InterfaceObjectDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + let objects = object_decl + .idents + .iter_mut() + .map(|ident| { + self.define( + ident, + parent, + AnyEntKind::Object(Object { + class: ObjectClass::Signal, + iface: None, + subtype: Subtype::new(self.universal_integer().into()), + has_default: false, + }), + object_decl.span, + ) + }) + .collect_vec(); + for object in &objects { + let actual_object = match &mut object_decl.mode { + ModeIndication::Simple(mode) => { + let (subtype, class) = + self.analyze_simple_mode_indication(scope, mode, diagnostics)?; + Object { + class, + iface: Some(ObjectInterface::simple( + object_decl.list_type, + mode.mode.as_ref().map(|mode| mode.item).unwrap_or_default(), + )), + subtype, + has_default: mode.expression.is_some(), + } + } + ModeIndication::View(view) => { + let (view_ent, subtype) = + self.analyze_mode_indication(scope, object, view, diagnostics)?; + Object { + class: ObjectClass::Signal, + iface: Some(ObjectInterface::Port(InterfaceMode::View(view_ent))), + subtype, + has_default: false, + } + } + }; + unsafe { + object.set_kind(AnyEntKind::Object(actual_object)); + } + } + + Ok(objects) + } + + /// Analyzes a mode view indication of the form + /// ```vhdl + /// foo : view s_axis of axi_stream + /// ``` + /// + /// This function resolves all used types and verifies them. + /// If the provided view describes an array but no actual array type is given, i.e.: + /// ```vhdl + /// multiple_foos : view (s_axis) + /// ``` + /// this function will declare an anonymous array indication with a single index and the + /// view's subtype as element, similar as if the view was declared like so: + /// ```vhdl + /// -- pseudo code + /// type anonymous is array (integer range <>) of axi_stream; + /// multiple_foos : view (s_axis) of anonymous + /// ``` + /// The anonymous array type will be marked as being linked to the interface declaration + /// (in the example above: the `anonymous` type is implicitly declared by `multiple_foos`) + fn analyze_mode_indication( + &self, + scope: &Scope<'a>, + object_ent: EntRef<'a>, + view: &mut ModeViewIndication, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult<(ViewEnt<'a>, Subtype<'a>)> { + let resolved = + self.name_resolve(scope, view.name.span, &mut view.name.item, diagnostics)?; + let view_ent = self.resolve_view_ent(&resolved, diagnostics, view.name.span)?; + let subtype = if let Some((_, ast_declared_subtype)) = &mut view.subtype_indication { + let declared_subtype = + self.resolve_subtype_indication(scope, ast_declared_subtype, diagnostics)?; + match view.kind { + ModeViewIndicationKind::Array => { + let Type::Array { + indexes: _, + elem_type, + } = declared_subtype.type_mark().kind() + else { + bail!( + diagnostics, + Diagnostic::new( + ast_declared_subtype.type_mark.pos(self.ctx), + "Subtype must be an array", + ErrorCode::TypeMismatch + ) + ); + }; + if *elem_type != view_ent.subtype().type_mark() { + bail!( + diagnostics, + Diagnostic::new( + ast_declared_subtype.type_mark.pos(self.ctx), + format!( + "Array element {} must match {} declared for the view", + elem_type.describe(), + view_ent.subtype().type_mark().describe() + ), + ErrorCode::TypeMismatch + ) + ); + } + } + ModeViewIndicationKind::Record => { + if declared_subtype.type_mark() != view_ent.subtype().type_mark() { + bail!( + diagnostics, + Diagnostic::new( + ast_declared_subtype.type_mark.pos(self.ctx), + "Specified subtype must match the subtype declared for the view", + ErrorCode::TypeMismatch + ) + ); + } + } + } + declared_subtype + } else { + match view.kind { + ModeViewIndicationKind::Array => { + let typ = Type::Array { + indexes: vec![Some(self.universal_integer())], + elem_type: view_ent.subtype().type_mark(), + }; + let typ = self.arena.implicit( + object_ent, + scope.anonymous_designator(), + AnyEntKind::Type(typ), + ); + Subtype::new(TypeEnt::from_any(typ).unwrap()) + } + ModeViewIndicationKind::Record => *view_ent.subtype(), + } + }; + Ok((view_ent, subtype)) + } + + pub fn analyze_simple_mode_indication( + &self, + scope: &Scope<'a>, + mode: &mut SimpleModeIndication, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult<(Subtype<'a>, ObjectClass)> { + let subtype = + self.resolve_subtype_indication(scope, &mut mode.subtype_indication, diagnostics); + + if let Some(ref mut expression) = mode.expression { + if let Ok(ref subtype) = subtype { + self.expr_pos_with_ttyp( + scope, + subtype.type_mark(), + expression.span, + &mut expression.item, + diagnostics, + )?; + } else { + self.expr_unknown_ttyp(scope, expression, diagnostics)? + } + } + + Ok((subtype?, mode.class)) + } + + pub fn analyze_interface_list( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + interface_list: &mut InterfaceList, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult> { + let mut params = FormalRegion::new(interface_list.interface_type); + + for decl in interface_list.items.iter_mut() { + if let Some(ents) = + as_fatal(self.analyze_interface_declaration(scope, parent, decl, diagnostics))? + { + for ent in ents { + scope.add(ent, diagnostics); + params.add(ent); + } + } + } + Ok(params) + } + + pub(crate) fn analyze_array_index( + &self, + scope: &Scope<'a>, + array_index: &mut ArrayIndex, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match array_index { + ArrayIndex::IndexSubtypeDefintion(ref mut type_mark) => self + .type_name(scope, type_mark.span, &mut type_mark.item, diagnostics) + .map(|typ| typ.base()), + ArrayIndex::Discrete(ref mut drange) => { + self.drange_type(scope, &mut drange.item, diagnostics) + } + } + } +} + +impl Diagnostic { + fn no_overloaded_with_signature( + pos: &SrcPos, + des: &Designator, + overloaded: &OverloadedName<'_>, + ) -> Diagnostic { + let mut diagnostic = Diagnostic::new( + pos, + format!( + "Could not find declaration of {} with given signature", + des.describe() + ), + ErrorCode::NoOverloadedWithSignature, + ); + diagnostic.add_subprogram_candidates("Found", overloaded.entities()); + diagnostic + } + + fn should_not_have_signature(prefix: &str, pos: impl AsRef) -> Diagnostic { + Diagnostic::new( + pos, + format!("{prefix} should only have a signature for subprograms and enum literals"), + ErrorCode::IllegalSignature, + ) + } + + fn signature_required(pos: impl AsRef) -> Diagnostic { + Diagnostic::new( + pos, + "Signature required for alias of subprogram and enum literals", + ErrorCode::SignatureRequired, + ) + } +} + +fn get_entity_class(ent: EntRef<'_>) -> Option { + match ent.actual_kind() { + // Alias is never the direct target of attribute + AnyEntKind::ExternalAlias { .. } => None, + // Alias is never the direct target of attribute + AnyEntKind::ObjectAlias { .. } => None, + AnyEntKind::File(_) => Some(EntityClass::File), + AnyEntKind::InterfaceFile(_) => Some(EntityClass::File), + AnyEntKind::Component(_) => Some(EntityClass::Component), + AnyEntKind::Attribute(_) => None, + AnyEntKind::Overloaded(ent) => match ent { + Overloaded::SubprogramDecl(s) + | Overloaded::Subprogram(s) + | Overloaded::UninstSubprogramDecl(s, _) + | Overloaded::UninstSubprogram(s, _) + | Overloaded::InterfaceSubprogram(s) => { + if s.return_type.is_some() { + Some(EntityClass::Function) + } else { + Some(EntityClass::Procedure) + } + } + Overloaded::EnumLiteral(_) => Some(EntityClass::Literal), + // Alias is never the direct target of attribute + Overloaded::Alias(_) => None, + }, + AnyEntKind::Type(Type::Subtype(_)) => Some(EntityClass::Subtype), + AnyEntKind::Type(_) => Some(EntityClass::Type), + AnyEntKind::ElementDeclaration(_) => None, + AnyEntKind::Concurrent(_) => Some(EntityClass::Label), + AnyEntKind::Sequential(_) => Some(EntityClass::Label), + AnyEntKind::Object(obj) => match obj.class { + ObjectClass::Signal => Some(EntityClass::Signal), + ObjectClass::Constant => Some(EntityClass::Constant), + ObjectClass::Variable => Some(EntityClass::Variable), + ObjectClass::SharedVariable => Some(EntityClass::Variable), + }, + AnyEntKind::LoopParameter(_) => None, // @TODO is it allowed? + AnyEntKind::PhysicalLiteral(_) => None, // @TODO maybe Units? + AnyEntKind::DeferredConstant(_) => Some(EntityClass::Constant), + AnyEntKind::Library => None, + AnyEntKind::Design(des) => match des { + Design::Entity(_, _) => Some(EntityClass::Entity), + Design::Architecture(..) => Some(EntityClass::Architecture), + Design::Configuration => Some(EntityClass::Configuration), + Design::Package(_, _) => Some(EntityClass::Package), + // Should never be target of attribute + Design::PackageBody(..) => None, + Design::UninstPackage(_, _) => None, + Design::PackageInstance(_) => None, + Design::InterfacePackageInstance(_) => None, + Design::Context(_) => None, + }, + AnyEntKind::View(_) => None, + } +} + +fn find_full_type_definition<'a>( + name: &Symbol, + decls: &'a [WithTokenSpan], +) -> Option<&'a TypeDeclaration> { + for decl in decls.iter() { + if let Declaration::Type(type_decl) = &decl.item { + match type_decl.def { + TypeDefinition::Incomplete(..) => { + // ignored + } + _ => { + if type_decl.ident.name() == name { + return Some(type_decl); + } + } + } + } + } + None +} + +const UNASSOCIATED_DISPLAY_THRESHOLD: usize = 3; + +/// Pretty formats a hash set with unassociated record elements. +/// This is for an improved user experience. +/// The returned message has the format "Missing association of x". +/// * If there is only one element, the message becomes "Missing association of element the_element" +/// * If there are more elements, the message becomes +/// "Missing association of element the_element1, the_element2 and the_element3" +/// * If there are more elements than [UNASSOCIATED_DISPLAY_THRESHOLD], the message will be truncated +/// to "Missing association of element the_element1, the_element2, the_element3 and 17 more" +fn pretty_format_unassociated_message(unassociated: &HashSet<&RecordElement<'_>>) -> String { + assert!( + !unassociated.is_empty(), + "Should never be called with an empty set" + ); + let mut as_string_vec = unassociated + .iter() + .sorted_by_key(|el| el.decl_pos()) + .map(|el| el.designator().describe()) + .collect_vec(); + let description = if as_string_vec.len() == 1 { + as_string_vec.pop().unwrap() + } else if as_string_vec.len() > UNASSOCIATED_DISPLAY_THRESHOLD { + let mut desc = as_string_vec[..UNASSOCIATED_DISPLAY_THRESHOLD].join(", "); + desc.push_str(" and "); + desc.push_str(&(as_string_vec.len() - UNASSOCIATED_DISPLAY_THRESHOLD).to_string()); + desc.push_str(" more"); + desc + } else { + // len > 1, therefore we always have a last element + let last = as_string_vec.pop().unwrap(); + let mut desc = as_string_vec.join(", "); + desc.push_str(" and "); + desc.push_str(&last); + desc + }; + if unassociated.len() == 1 { + format!("Missing association of element {}", description) + } else { + format!("Missing association of elements {}", description) + } +} diff --git a/vhdl_lang/src/analysis/design_unit.rs b/vhdl_lang/src/analysis/design_unit.rs new file mode 100644 index 0000000..1b02ffa --- /dev/null +++ b/vhdl_lang/src/analysis/design_unit.rs @@ -0,0 +1,747 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::analysis::names::ResolvedName; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use crate::HasTokenSpan; +use analyze::*; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn analyze_primary_unit( + &self, + unit: &mut AnyPrimaryUnit, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match unit { + AnyPrimaryUnit::Entity(unit) => self.analyze_entity(unit, diagnostics), + AnyPrimaryUnit::Configuration(unit) => self.analyze_configuration(unit, diagnostics), + AnyPrimaryUnit::Package(unit) => self.analyze_package(unit, diagnostics), + AnyPrimaryUnit::PackageInstance(unit) => { + self.analyze_package_instance(unit, diagnostics) + } + AnyPrimaryUnit::Context(unit) => self.analyze_context(unit, diagnostics), + } + } + + pub fn analyze_secondary_unit( + &self, + unit: &mut AnySecondaryUnit, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match unit { + AnySecondaryUnit::Architecture(unit) => self.analyze_architecture(unit, diagnostics), + AnySecondaryUnit::PackageBody(unit) => self.analyze_package_body(unit, diagnostics), + } + } + + fn analyze_entity( + &self, + unit: &mut EntityDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + // Pre-define entity and overwrite it later + let ent = self.arena.explicit( + unit.name().clone(), + self.work_library(), + AnyEntKind::Design(Design::Entity(Visibility::default(), Region::default())), + Some(unit.ident_pos(self.ctx)), + unit.span(), + Some(self.source()), + ); + + unit.ident.decl.set(ent.id()); + let root_scope = Scope::default(); + self.add_implicit_context_clause(&root_scope)?; + self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?; + + root_scope.add(ent, diagnostics); + let primary_scope = root_scope.nested(); + + if let Some(ref mut list) = unit.generic_clause { + self.analyze_interface_list(&primary_scope, ent, list, diagnostics)?; + } + if let Some(ref mut list) = unit.port_clause { + self.analyze_interface_list(&primary_scope, ent, list, diagnostics)?; + } + self.define_labels_for_concurrent_part( + &primary_scope, + ent, + &mut unit.statements, + diagnostics, + )?; + self.analyze_declarative_part(&primary_scope, ent, &mut unit.decl, diagnostics)?; + self.analyze_concurrent_part(&primary_scope, ent, &mut unit.statements, diagnostics)?; + + let region = primary_scope.into_region(); + let visibility = root_scope.into_visibility(); + + let kind = AnyEntKind::Design(Design::Entity(visibility, region)); + unsafe { ent.set_kind(kind) } + + Ok(()) + } + + fn analyze_configuration( + &self, + unit: &mut ConfigurationDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let src_span = unit.span(); + let root_region = Scope::default(); + self.add_implicit_context_clause(&root_region)?; + self.analyze_context_clause(&root_region, &mut unit.context_clause, diagnostics)?; + + if let Some(named_entity) = + as_fatal(self.lookup_entity_for_configuration(&root_region, unit, diagnostics))? + { + if let Some(primary_pos) = named_entity.decl_pos() { + let secondary_pos = unit.ident_pos(self.ctx); + if primary_pos.source == secondary_pos.source + && primary_pos.start() > secondary_pos.start() + { + diagnostics.add( + secondary_pos, + capitalize(&format!( + "{} declared before {}", + self.current_unit_id().describe(), + named_entity.describe() + )), + ErrorCode::DeclaredBefore, + ); + } + } + }; + + self.define( + &mut unit.ident, + self.work_library(), + AnyEntKind::Design(Design::Configuration), + src_span, + ); + + Ok(()) + } + + fn analyze_package( + &self, + unit: &mut PackageDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let ent = self.arena.explicit( + unit.name().clone(), + self.work_library(), + AnyEntKind::Design(Design::Package(Visibility::default(), Region::default())), + Some(unit.ident_pos(self.ctx)), + unit.span(), + Some(self.source()), + ); + + unit.ident.decl.set(ent.id()); + + let root_scope = Scope::default(); + self.add_implicit_context_clause(&root_scope)?; + self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?; + + root_scope.add(ent, diagnostics); + let scope = root_scope.nested().in_package_declaration(); + + if let Some(ref mut list) = unit.generic_clause { + self.analyze_interface_list(&scope, ent, list, diagnostics)?; + } + self.analyze_declarative_part(&scope, ent, &mut unit.decl, diagnostics)?; + + if !self.has_package_body() { + scope.close(diagnostics); + } + + let region = scope.into_region(); + let visibility = root_scope.into_visibility(); + + let kind = if unit.generic_clause.is_some() { + AnyEntKind::Design(Design::UninstPackage(visibility, region)) + } else { + AnyEntKind::Design(Design::Package(visibility, region)) + }; + + unsafe { + ent.set_kind(kind); + } + + Ok(()) + } + + fn analyze_package_instance( + &self, + unit: &mut PackageInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let ent = self.arena.explicit( + unit.name().clone(), + self.work_library(), + AnyEntKind::Design(Design::PackageInstance(Region::default())), + Some(unit.ident_pos(self.ctx)), + unit.span(), + Some(self.source()), + ); + + unit.ident.decl.set(ent.id()); + let root_scope = Scope::default(); + self.add_implicit_context_clause(&root_scope)?; + + self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?; + + if let Some(pkg_region) = + as_fatal(self.generic_package_instance(&root_scope, ent, unit, diagnostics))? + { + let kind = AnyEntKind::Design(Design::PackageInstance(pkg_region)); + + unsafe { + ent.set_kind(kind); + } + } + + Ok(()) + } + + fn analyze_context( + &self, + unit: &mut ContextDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let root_scope = Scope::default(); + self.add_implicit_context_clause(&root_scope)?; + let scope = root_scope.nested(); + let src_span = unit.span(); + self.analyze_context_clause(&scope, &mut unit.items, diagnostics)?; + + self.define( + &mut unit.ident, + self.work_library(), + AnyEntKind::Design(Design::Context(scope.into_region())), + src_span, + ); + + Ok(()) + } + + fn analyze_architecture( + &self, + unit: &mut ArchitectureBody, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let src_span = unit.span(); + let Some(primary) = as_fatal(self.lookup_in_library( + diagnostics, + self.work_library_name(), + unit.entity_name.item.pos(self.ctx), + &Designator::Identifier(unit.entity_name.item.item.clone()), + ))? + else { + return Ok(()); + }; + unit.entity_name.set_unique_reference(primary.into()); + self.check_secondary_before_primary(&primary, unit.ident_pos(self.ctx), diagnostics); + + let (visibility, region) = + if let Design::Entity(ref visibility, ref region) = primary.kind() { + (visibility, region) + } else { + let mut diagnostic = + Diagnostic::mismatched_kinds(unit.ident_pos(self.ctx), "Expected an entity"); + + if let Some(pos) = primary.decl_pos() { + diagnostic.add_related(pos, format!("Found {}", primary.describe())) + } + + return Ok(()); + }; + + let root_scope = Scope::new(Region::with_visibility(visibility.clone())); + self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?; + + let arch = self.define( + &mut unit.ident, + primary.into(), + AnyEntKind::Design(Design::Architecture( + Visibility::default(), + Region::default(), + primary, + )), + src_span, + ); + + root_scope.add(arch, diagnostics); + + let scope = Scope::extend(region, Some(&root_scope)); + + // Entity name is visible + scope.make_potentially_visible(primary.decl_pos(), primary.into()); + + self.define_labels_for_concurrent_part(&scope, arch, &mut unit.statements, diagnostics)?; + self.analyze_declarative_part(&scope, arch, &mut unit.decl, diagnostics)?; + self.analyze_concurrent_part(&scope, arch, &mut unit.statements, diagnostics)?; + scope.close(diagnostics); + + let region = scope.into_region(); + let visibility = root_scope.into_visibility(); + + unsafe { + arch.set_kind(AnyEntKind::Design(Design::Architecture( + visibility, region, primary, + ))) + } + Ok(()) + } + + fn analyze_package_body( + &self, + unit: &mut PackageBody, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let Some(primary) = as_fatal(self.lookup_in_library( + diagnostics, + self.work_library_name(), + unit.ident_pos(self.ctx), + &Designator::Identifier(unit.ident.tree.item.clone()), + ))? + else { + return Ok(()); + }; + + let (visibility, region) = match primary.kind() { + Design::Package(ref visibility, ref region) + | Design::UninstPackage(ref visibility, ref region) => (visibility, region), + _ => { + let mut diagnostic = + Diagnostic::mismatched_kinds(unit.ident_pos(self.ctx), "Expected a package"); + + if let Some(pos) = primary.decl_pos() { + diagnostic.add_related(pos, format!("Found {}", primary.describe())) + } + + return Ok(()); + } + }; + + let body = self.arena.alloc( + unit.ident.name().clone().into(), + Some(self.work_library()), + Related::DeclaredBy(primary.into()), + AnyEntKind::Design(Design::PackageBody( + Visibility::default(), + Region::default(), + )), + Some(unit.ident_pos(self.ctx).clone()), + unit.span(), + Some(self.source()), + ); + unit.ident.decl.set(body.id()); + + self.check_secondary_before_primary(&primary, unit.ident_pos(self.ctx), diagnostics); + + // @TODO make pattern of primary/secondary extension + let root_scope = Scope::new(Region::with_visibility(visibility.clone())); + + self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?; + + let scope = Scope::extend(region, Some(&root_scope)); + + // Package name is visible + scope.make_potentially_visible(primary.decl_pos(), primary.into()); + + self.analyze_declarative_part(&scope, body, &mut unit.decl, diagnostics)?; + scope.close(diagnostics); + let region = scope.into_region(); + let visibility = root_scope.into_visibility(); + + unsafe { body.set_kind(AnyEntKind::Design(Design::PackageBody(visibility, region))) } + Ok(()) + } + + fn check_secondary_before_primary( + &self, + primary: &DesignEnt<'_>, + secondary_pos: &SrcPos, + diagnostics: &mut dyn DiagnosticHandler, + ) { + if let Some(primary_pos) = primary.decl_pos() { + if primary_pos.source == secondary_pos.source + && primary_pos.start() > secondary_pos.start() + { + diagnostics.add( + secondary_pos, + format!( + "{} declared before {}", + capitalize(&self.current_unit_id().describe()), + primary.describe(), + ), + ErrorCode::DeclaredBefore, + ); + } + } + } + + fn lookup_entity_for_configuration( + &self, + scope: &Scope<'a>, + config: &mut ConfigurationDeclaration, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let ent_name = &mut config.entity_name; + let ent_name_span = ent_name.span; + + match ent_name.item { + // Entities are implicitly defined for configurations + // configuration cfg of ent + Name::Designator(ref mut designator) => Ok(self + .lookup_in_library( + diagnostics, + self.work_library_name(), + &ent_name_span.pos(self.ctx), + &designator.item, + ) + .map(|design| { + designator.set_unique_reference(design.into()); + design + })?), + + // configuration cfg of lib.ent + Name::Selected(ref mut prefix, ref mut designator) => { + let name = self.name_resolve(scope, prefix.span, &mut prefix.item, diagnostics)?; + match name { + ResolvedName::Library(ref library_name) => { + if library_name != self.work_library_name() { + diagnostics.add( + prefix.pos(self.ctx), + format!("Configuration must be within the same library '{}' as the corresponding entity", self.work_library_name()), ErrorCode::ConfigNotInSameLibrary); + Err(EvalError::Unknown) + } else { + let primary_ent = self.lookup_in_library( + diagnostics, + library_name, + designator.pos(self.ctx), + &designator.item.item, + )?; + designator + .item + .reference + .set_unique_reference(primary_ent.into()); + match primary_ent.kind() { + Design::Entity(..) => Ok(primary_ent), + _ => { + diagnostics.add( + designator.pos(self.ctx), + format!( + "{} does not denote an entity", + primary_ent.describe() + ), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } + } + } + other => { + diagnostics.push(other.kind_error(prefix.pos(self.ctx), "library")); + Err(EvalError::Unknown) + } + } + } + _ => { + diagnostics.add( + ent_name.pos(self.ctx), + "Expected selected name", + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } + } + + fn resolve_context_item_prefix( + &self, + diagnostics: &mut dyn DiagnosticHandler, + scope: &Scope<'a>, + prefix: &mut WithTokenSpan, + ) -> EvalResult> { + match self.resolve_context_item_name(diagnostics, scope, prefix)? { + UsedNames::Single(visible) => match visible.into_non_overloaded() { + Ok(ent_ref) => Ok(ent_ref), + Err(_) => { + bail!( + diagnostics, + Diagnostic::mismatched_kinds( + prefix.pos(self.ctx), + "Invalid prefix of a selected name", + ) + ); + } + }, + UsedNames::AllWithin(..) => { + bail!( + diagnostics, + Diagnostic::mismatched_kinds( + prefix.pos(self.ctx), + "'.all' may not be the prefix of a selected name", + ) + ); + } + } + } + + fn resolve_context_item_name( + &self, + diagnostics: &mut dyn DiagnosticHandler, + scope: &Scope<'a>, + name: &mut WithTokenSpan, + ) -> EvalResult> { + match &mut name.item { + Name::Selected(ref mut prefix, ref mut suffix) => { + let prefix_ent = self.resolve_context_item_prefix(diagnostics, scope, prefix)?; + + let visible = self.lookup_selected(diagnostics, prefix.span, prefix_ent, suffix)?; + suffix.set_reference(&visible); + Ok(UsedNames::Single(visible)) + } + + Name::SelectedAll(prefix) => { + let prefix_ent = self.resolve_context_item_prefix(diagnostics, scope, prefix)?; + Ok(UsedNames::AllWithin(prefix.pos(self.ctx), prefix_ent)) + } + Name::Designator(designator) => { + let visible = scope + .lookup(designator.designator()) + .map_err(|err| err.into_diagnostic(self.ctx, name.span)) + .into_eval_result(diagnostics)?; + designator.set_reference(&visible); + Ok(UsedNames::Single(visible)) + } + + Name::Slice(..) + | Name::Attribute(..) + | Name::CallOrIndexed(..) + | Name::External(..) => { + bail!( + diagnostics, + Diagnostic::mismatched_kinds(name.pos(self.ctx), "Invalid selected name",) + ); + } + } + } + + pub(crate) fn analyze_context_clause( + &self, + scope: &Scope<'a>, + context_clause: &mut [ContextItem], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for context_item in context_clause.iter_mut() { + match context_item { + ContextItem::Library(LibraryClause { + ref mut name_list, .. + }) => { + for library_name in name_list.iter_mut() { + if self.work_sym == library_name.item.item { + library_name.set_unique_reference(self.work_library()); + diagnostics.add( + library_name.item.pos(self.ctx), + "Library clause not necessary for current working library", + ErrorCode::UnnecessaryWorkLibrary, + ) + } else if let Some(library) = self.get_library(&library_name.item.item) { + library_name.set_unique_reference(library); + scope.make_potentially_visible( + Some(library_name.item.pos(self.ctx)), + library, + ); + } else { + diagnostics.add( + library_name.item.pos(self.ctx), + format!("No such library '{}'", library_name.item), + ErrorCode::Unresolved, + ); + } + } + } + ContextItem::Use(ref mut use_clause) => { + self.analyze_use_clause(scope, use_clause, diagnostics)?; + } + ContextItem::Context(ContextReference { + ref mut name_list, .. + }) => { + for name in name_list.iter_mut() { + match name.item { + Name::Selected(..) => {} + _ => { + diagnostics.add( + name.pos(self.ctx), + "Context reference must be a selected name", + ErrorCode::MismatchedKinds, + ); + continue; + } + } + + let Some(context_item) = + as_fatal(self.resolve_context_item_name(diagnostics, scope, name))? + else { + continue; + }; + + match context_item { + UsedNames::Single(visible) => { + let ent = visible.first(); + match ent.kind() { + // OK + AnyEntKind::Design(Design::Context(ref context_region)) => { + scope.add_context_visibility( + Some(&name.pos(self.ctx)), + context_region, + ); + } + _ => { + if let Name::Selected(_, ref suffix) = name.item { + diagnostics.add( + suffix.pos(self.ctx), + format!( + "{} does not denote a context declaration", + ent.describe() + ), + ErrorCode::MismatchedKinds, + ); + } + } + } + } + UsedNames::AllWithin(..) => { + // Handled above + } + } + } + } + } + } + + Ok(()) + } + + pub fn analyze_use_clause( + &self, + scope: &Scope<'a>, + use_clause: &mut UseClause, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for name in use_clause.name_list.iter_mut() { + match name.item { + Name::Selected(..) => {} + Name::SelectedAll(..) => {} + _ => { + diagnostics.add( + name.pos(self.ctx), + "Use clause must be a selected name", + ErrorCode::MismatchedKinds, + ); + // We still want to resolve the name, + // so that it is available for completion purposes. + // We ignore the errors here, since there is already a diagnostic at that position. + let mut empty_diag = Vec::new(); + let _ = self.name_resolve(scope, name.span(), &mut name.item, &mut empty_diag); + continue; + } + } + + let Some(context_item) = + as_fatal(self.resolve_context_item_name(diagnostics, scope, name))? + else { + continue; + }; + match context_item { + UsedNames::Single(visible) => { + visible.make_potentially_visible_in(Some(&name.pos(self.ctx)), scope); + } + UsedNames::AllWithin(visibility_pos, named_entity) => match named_entity.kind() { + AnyEntKind::Library => { + let library_name = named_entity.designator().expect_identifier(); + self.use_all_in_library(&name.pos(self.ctx), library_name, scope)?; + } + AnyEntKind::Design(design) => match design { + Design::UninstPackage(..) => { + diagnostics.push(Diagnostic::invalid_selected_name_prefix( + named_entity, + &visibility_pos, + )); + } + Design::Package(_, ref primary_region) + | Design::PackageInstance(ref primary_region) + | Design::InterfacePackageInstance(ref primary_region) => { + scope.make_all_potentially_visible( + Some(&name.pos(self.ctx)), + primary_region, + ); + } + _ => { + diagnostics.add( + visibility_pos, + "Invalid prefix for selected name", + ErrorCode::MismatchedKinds, + ); + } + }, + + _ => { + diagnostics.add( + visibility_pos, + "Invalid prefix for selected name", + ErrorCode::MismatchedKinds, + ); + } + }, + } + } + + Ok(()) + } + + /// Returns a reference to the uninstantiated package + pub fn analyze_package_instance_name( + &self, + scope: &Scope<'a>, + package_name: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let name = self.name_resolve( + scope, + package_name.span, + &mut package_name.item, + diagnostics, + )?; + if let ResolvedName::Design(ref unit) = name { + if let AnyEntKind::Design(Design::UninstPackage(_, package_region)) = &unit.kind { + return Ok(package_region.clone()); + } + } + diagnostics.add( + package_name.pos(self.ctx), + format!("'{package_name}' is not an uninstantiated generic package"), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } +} + +pub enum UsedNames<'a> { + /// A single name was used selected + Single(NamedEntities<'a>), + /// All names within was selected + /// @TODO add pos for where declaration was made visible into VisibleDeclaration + AllWithin(SrcPos, EntRef<'a>), +} diff --git a/vhdl_lang/src/analysis/expression.rs b/vhdl_lang/src/analysis/expression.rs new file mode 100644 index 0000000..afc8731 --- /dev/null +++ b/vhdl_lang/src/analysis/expression.rs @@ -0,0 +1,1676 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use fnv::FnvHashMap; +use fnv::FnvHashSet; +use vhdl_lang::TokenAccess; + +use super::analyze::*; +use super::overloaded::Disambiguated; +use super::overloaded::DisambiguatedType; +use super::overloaded::ResolvedCall; +use super::scope::*; +use crate::ast::token_range::{WithToken, WithTokenSpan}; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use crate::{TokenId, TokenSpan}; + +#[derive(Debug, PartialEq, Eq)] +pub enum ExpressionType<'a> { + Unambiguous(TypeEnt<'a>), + Ambiguous(FnvHashSet>), + String, + Null, + Aggregate, +} + +impl<'a> ExpressionType<'a> { + fn match_type(&self, other: BaseType<'a>) -> bool { + match self { + ExpressionType::Unambiguous(typ) => typ.base() == other, + ExpressionType::Ambiguous(types) => types.contains(&other), + ExpressionType::String => other.is_compatible_with_string_literal(), + ExpressionType::Null => other.is_access(), + ExpressionType::Aggregate => other.is_composite(), + } + } + + pub(crate) fn describe(&self) -> String { + match self { + ExpressionType::Unambiguous(typ) => format!("expression with {}", typ.describe()), + ExpressionType::Ambiguous(_) => "ambiguous expression".to_owned(), + ExpressionType::String => "string literal".to_owned(), + ExpressionType::Null => "null literal".to_owned(), + ExpressionType::Aggregate => "aggregate expression".to_owned(), + } + } +} + +impl<'a> From> for ExpressionType<'a> { + fn from(value: DisambiguatedType<'a>) -> Self { + match value { + DisambiguatedType::Unambiguous(typ) => ExpressionType::Unambiguous(typ), + DisambiguatedType::Ambiguous(types) => ExpressionType::Ambiguous(types), + } + } +} + +pub(super) struct TypeMatcher<'c, 'a, 't> { + // Allow implicit type conversion from universal real/integer to other integer types + implicit_type_conversion: bool, + + // Allow implicit type conversion from abstract types to universal real/integer + implicit_type_conversion_from_universal: bool, + context: &'c AnalyzeContext<'a, 't>, +} + +impl<'c, 'a, 't> TypeMatcher<'c, 'a, 't> { + // Returns true if the expression types is possible given the target type + pub fn is_possible(&self, types: &ExpressionType<'a>, ttyp: BaseType<'a>) -> bool { + if types.match_type(ttyp) { + true + } else if self.implicit_type_conversion { + match ttyp.kind() { + Type::Integer => types.match_type(self.context.universal_integer()), + Type::Real => types.match_type(self.context.universal_real()), + Type::Universal(UniversalType::Integer) + if self.implicit_type_conversion_from_universal => + { + match types { + ExpressionType::Unambiguous(typ) => typ.base().is_any_integer(), + ExpressionType::Ambiguous(types) => { + types.iter().any(|typ| typ.is_any_integer()) + } + _ => false, + } + } + Type::Universal(UniversalType::Real) + if self.implicit_type_conversion_from_universal => + { + match types { + ExpressionType::Unambiguous(typ) => typ.base().is_any_real(), + ExpressionType::Ambiguous(types) => { + types.iter().any(|typ| typ.is_any_real()) + } + _ => false, + } + } + _ => false, + } + } else { + false + } + } + + pub fn can_be_target_type(&self, typ: TypeEnt<'a>, ttyp: BaseType<'a>) -> bool { + self.is_possible(&ExpressionType::Unambiguous(typ), ttyp) + } + + pub fn disambiguate_op_by_arguments( + &self, + candidates: &mut Vec>, + operand_types: &[ExpressionType<'a>], + ) { + candidates.retain(|ent| { + operand_types + .iter() + .enumerate() + .all(|(idx, expr_type)| self.is_possible(expr_type, ent.nth_base(idx).unwrap())) + }) + } + + pub fn disambiguate_by_assoc_types( + &self, + actual_types: &[Option>], + candidates: &mut Vec>, + ) { + candidates.retain(|resolved| { + actual_types.iter().enumerate().all(|(idx, actual_type)| { + if let Some(actual_type) = actual_type { + self.is_possible(actual_type, resolved.formals[idx].base()) + } else { + true + } + }) + }) + } + + pub fn disambiguate_op_by_return_type( + &self, + candidates: &mut Vec>>, + ttyp: Option>, // Optional target type constraint + ) { + let tbase = ttyp.map(|ttyp| ttyp.base()); + candidates.retain(|ent| { + if let Some(tbase) = tbase { + self.can_be_target_type(ent.as_ref().return_type().unwrap(), tbase) + } else { + true + } + }); + } +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn strict_matcher(&self) -> TypeMatcher<'_, 'a, 't> { + TypeMatcher { + implicit_type_conversion: false, + implicit_type_conversion_from_universal: false, + context: self, + } + } + + pub fn any_matcher(&self) -> TypeMatcher<'_, 'a, 't> { + TypeMatcher { + implicit_type_conversion: true, + implicit_type_conversion_from_universal: true, + context: self, + } + } + + pub fn implicit_matcher(&self) -> TypeMatcher<'_, 'a, 't> { + TypeMatcher { + implicit_type_conversion: true, + implicit_type_conversion_from_universal: false, + context: self, + } + } + + // Returns true if the expression types is possible given the target type + pub fn is_possible(&self, types: &ExpressionType<'a>, ttyp: BaseType<'a>) -> bool { + self.any_matcher().is_possible(types, ttyp) + } + + pub fn common_type(&self, typ1: BaseType<'a>, typ2: BaseType<'a>) -> Option> { + if typ1.id() == typ2.id() { + Some(typ1) + } else if typ1.is_universal_of(typ2) { + Some(typ2) + } else if typ2.is_universal_of(typ1) { + Some(typ1) + } else { + None + } + } + + pub fn common_types( + &self, + types: FnvHashSet>, + typ: BaseType<'a>, + ) -> FnvHashSet> { + types + .into_iter() + .filter_map(|t| self.common_type(t, typ)) + .collect() + } + + pub fn can_be_target_type(&self, typ: TypeEnt<'a>, ttyp: BaseType<'a>) -> bool { + self.any_matcher().can_be_target_type(typ, ttyp) + } + + pub fn expr_unknown_ttyp( + &self, + scope: &Scope<'a>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + self.expr_pos_unknown_ttyp(scope, expr.span, &mut expr.item, diagnostics) + } + + pub fn expr_unambiguous_type( + &self, + scope: &Scope<'a>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match self.expr_type(scope, expr, diagnostics)? { + ExpressionType::Unambiguous(typ) => Ok(typ), + ExpressionType::Ambiguous(_) + | ExpressionType::String + | ExpressionType::Null + | ExpressionType::Aggregate => { + diagnostics.add( + expr.pos(self.ctx), + "Ambiguous expression. You can use a qualified expression type'(expr) to disambiguate.", + ErrorCode::AmbiguousExpression, + ); + Err(EvalError::Unknown) + } + } + } + + pub fn lookup_operator( + &self, + diagnostics: &mut dyn DiagnosticHandler, + scope: &Scope<'a>, + op_pos: TokenId, + op: Operator, + arity: usize, + ) -> EvalResult>> { + let designator = Designator::OperatorSymbol(op); + match scope + .lookup(&designator) + .map_err(|err| err.into_diagnostic(self.ctx, op_pos)) + .into_eval_result(diagnostics)? + { + NamedEntities::Single(ent) => { + // Should never happen but better know if it does + bail!( + diagnostics, + Diagnostic::internal( + op_pos.pos(self.ctx), + format!( + "Operator symbol cannot denote non-overloaded symbol {}", + ent.describe(), + ), + ) + ); + } + NamedEntities::Overloaded(overloaded) => { + // Candidates that match arity of operator + let op_candidates: Vec<_> = overloaded + .entities() + .filter(|ent| ent.formals().len() == arity && ent.return_type().is_some()) + .collect(); + + if op_candidates.is_empty() { + bail!( + diagnostics, + Diagnostic::new( + self.ctx.get_pos(op_pos), + format!("Found no match for {}", designator.describe()), + ErrorCode::Unresolved, + ) + ); + } else { + Ok(op_candidates) + } + } + } + } + + pub fn operand_types( + &self, + scope: &Scope<'a>, + operands: &mut [&mut WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + let mut operand_types = Vec::with_capacity(operands.len()); + for expr in operands.iter_mut() { + operand_types.push(self.expr_type(scope, expr, diagnostics)?); + } + Ok(operand_types) + } + + pub fn check_op( + &self, + scope: &Scope<'a>, + op: &mut WithToken>, + overloaded: OverloadedEnt<'a>, + exprs: &mut [&mut WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + op.set_unique_reference(&overloaded); + for (idx, expr) in exprs.iter_mut().enumerate() { + let target_type = overloaded.formals().nth(idx).unwrap().type_mark(); + self.expr_pos_with_ttyp(scope, target_type, expr.span, &mut expr.item, diagnostics)?; + } + Ok(()) + } + + pub fn disambiguate_op( + &self, + scope: &Scope<'a>, + ttyp: Option>, // Optional target type constraint + op: &mut WithToken>, + overloaded: Vec>, + exprs: &mut [&mut WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + // @TODO lookup already set reference to get O(N) instead of O(N^2) when disambiguating deeply nested ambiguous operators + if let Some(reference) = op.item.reference.get() { + if let Some(ent) = OverloadedEnt::from_any(self.arena.get(reference)) { + return Ok(Disambiguated::Unambiguous(ent)); + } + } + + let designator = Designator::OperatorSymbol(op.item.item); + let operand_types = self.operand_types(scope, exprs, diagnostics)?; + + let mut candidates = overloaded.clone(); + + if candidates.len() > 1 { + self.implicit_matcher() + .disambiguate_op_by_arguments(&mut candidates, &operand_types); + } + + if candidates.len() > 1 && ttyp.is_some() { + self.implicit_matcher() + .disambiguate_op_by_return_type(&mut candidates, ttyp); + } + + // Try to further disambiguate by removing implicit universal casts + if candidates.len() > 1 { + let return_types: FnvHashSet<_> = candidates + .iter() + .map(|c| c.return_type().unwrap().base()) + .collect(); + + if return_types.len() == 1 { + self.strict_matcher() + .disambiguate_op_by_arguments(&mut candidates, &operand_types); + } + } + + if candidates.len() > 1 { + if ttyp.is_some() { + self.strict_matcher() + .disambiguate_op_by_return_type(&mut candidates, ttyp); + } else { + let return_types: FnvHashSet<_> = candidates + .iter() + .map(|c| c.return_type().unwrap().base()) + .collect(); + + // Remove INTEGER if universal integer is a candidate + candidates.retain(|cand| { + if let Some(univ) = self.as_universal(cand.return_type().unwrap().base()) { + !return_types.contains(&univ) + } else { + true + } + }) + } + } + + if candidates.is_empty() { + // Try to disambiguate to a single candidate if return type ruled out all candidates + // But it is unambiguous without implicit argument cast + let mut cands = overloaded.clone(); + self.strict_matcher() + .disambiguate_op_by_arguments(&mut cands, &operand_types); + + if cands.len() == 1 { + candidates = cands; + } + } + + if candidates.is_empty() { + diagnostics.add( + self.ctx.get_pos(op.token), + format!("Found no match for {}", designator.describe()), + ErrorCode::Unresolved, + ); + + Err(EvalError::Unknown) + } else if candidates.len() == 1 { + let ent = candidates[0]; + self.check_op(scope, op, ent, exprs, diagnostics)?; + Ok(Disambiguated::Unambiguous(ent)) + } else { + Ok(Disambiguated::Ambiguous(candidates)) + } + } + + fn as_universal(&self, typ: BaseType<'a>) -> Option> { + match typ.kind() { + Type::Integer => Some(self.universal_integer()), + Type::Real => Some(self.universal_real()), + _ => None, + } + } + + pub fn operator_type( + &self, + scope: &Scope<'a>, + op: &mut WithToken>, + exprs: &mut [&mut WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let op_candidates = + self.lookup_operator(diagnostics, scope, op.token, op.item.item, exprs.len())?; + + match self.disambiguate_op(scope, None, op, op_candidates, exprs, diagnostics)? { + Disambiguated::Unambiguous(overloaded) => Ok(ExpressionType::Unambiguous( + overloaded.return_type().unwrap(), + )), + Disambiguated::Ambiguous(overloaded) => Ok(ExpressionType::Ambiguous( + overloaded + .into_iter() + .map(|o| o.return_type().unwrap().base()) + .collect(), + )), + } + } + + pub fn expr_type( + &self, + scope: &Scope<'a>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + self.expr_pos_type(scope, expr.span, &mut expr.item, diagnostics) + } + + pub fn expr_pos_type( + &self, + scope: &Scope<'a>, + span: TokenSpan, + expr: &mut Expression, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match expr { + Expression::Binary(ref mut op, ref mut left, ref mut right) => { + self.operator_type(scope, op, &mut [left.as_mut(), right.as_mut()], diagnostics) + } + Expression::Unary(ref mut op, ref mut inner) => { + self.operator_type(scope, op, &mut [inner.as_mut()], diagnostics) + } + Expression::Name(ref mut name) => self + .expression_name_types(scope, span, name.as_mut(), diagnostics) + .map(ExpressionType::from), + Expression::Aggregate(_) => Ok(ExpressionType::Aggregate), + Expression::Qualified(ref mut qexpr) => { + let typ = self.analyze_qualified_expression(scope, qexpr, diagnostics)?; + Ok(ExpressionType::Unambiguous(typ)) + } + Expression::New(ref mut alloc) => match &mut alloc.item { + Allocator::Qualified(ref mut qexpr) => { + let typ = self.analyze_qualified_expression(scope, qexpr, diagnostics)?; + Ok(ExpressionType::Unambiguous(typ)) + } + Allocator::Subtype(ref mut subtype) => self + .resolve_subtype_indication(scope, subtype, diagnostics) + .map(|typ| ExpressionType::Unambiguous(typ.type_mark())), + }, + Expression::Parenthesized(expr) => { + self.expr_pos_type(scope, expr.span, &mut expr.item, diagnostics) + } + Expression::Literal(ref mut literal) => match literal { + Literal::Physical(PhysicalLiteral { ref mut unit, .. }) => { + match self.resolve_physical_unit(scope, unit) { + Ok(typ) => Ok(ExpressionType::Unambiguous(typ)), + Err(err) => { + diagnostics.push(err); + Err(EvalError::Unknown) + } + } + } + Literal::String(_) => Ok(ExpressionType::String), + Literal::BitString(_) => Ok(ExpressionType::String), + Literal::Character(chr) => { + match scope.lookup(&Designator::Character(*chr)) { + Ok(NamedEntities::Single(ent)) => { + // Should never happen but better know if it does + diagnostics.add( + span.pos(self.ctx), + format!( + "Character literal cannot denote non-overloaded symbol {}", + ent.describe(), + ), + ErrorCode::Internal, + ); + Err(EvalError::Unknown) + } + Ok(NamedEntities::Overloaded(overloaded)) => { + if overloaded.len() == 1 { + let ent = overloaded.first(); + if let Some(return_type) = ent.return_type() { + Ok(ExpressionType::Unambiguous(return_type)) + } else { + diagnostics.add( + span.pos(self.ctx), + format!( + "Character literal cannot denote procedure symbol {}", + ent.describe(), + ), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } else { + Ok(ExpressionType::Ambiguous( + overloaded + .entities() + .flat_map(|e| e.return_type()) + .map(BaseType::from) + .collect(), + )) + } + } + Err(e) => { + diagnostics.push(e.into_diagnostic(self.ctx, span)); + Err(EvalError::Unknown) + } + } + } + Literal::AbstractLiteral(AbstractLiteral::Integer(_)) => { + Ok(ExpressionType::Unambiguous(self.universal_integer().into())) + } + Literal::AbstractLiteral(AbstractLiteral::Real(_)) => { + Ok(ExpressionType::Unambiguous(self.universal_real().into())) + } + Literal::Null => Ok(ExpressionType::Null), + }, + } + } + + // Fallback for analyzing an expression without a known target type + pub fn expr_pos_unknown_ttyp( + &self, + scope: &Scope<'a>, + span: TokenSpan, + expr: &mut Expression, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + as_fatal(self.expr_pos_type(scope, span, expr, diagnostics))?; + Ok(()) + } + + fn analyze_qualified_expression( + &self, + scope: &Scope<'a>, + qexpr: &mut QualifiedExpression, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let QualifiedExpression { type_mark, expr } = qexpr; + + match as_fatal(self.type_name(scope, type_mark.span, &mut type_mark.item, diagnostics))? { + Some(target_type) => { + self.expr_pos_with_ttyp( + scope, + target_type, + expr.span, + &mut expr.item, + diagnostics, + )?; + Ok(target_type) + } + None => { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + Err(EvalError::Unknown) + } + } + } + + pub fn analyze_allocation( + &self, + scope: &Scope<'a>, + alloc: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match &mut alloc.item { + Allocator::Qualified(ref mut qexpr) => { + as_fatal(self.analyze_qualified_expression(scope, qexpr, diagnostics))?; + } + Allocator::Subtype(ref mut subtype) => { + self.analyze_subtype_indication(scope, subtype, diagnostics)?; + } + } + Ok(()) + } + + pub fn expr_with_ttyp( + &self, + scope: &Scope<'a>, + target_type: TypeEnt<'a>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + self.expr_pos_with_ttyp(scope, target_type, expr.span, &mut expr.item, diagnostics) + } + + fn implicit_bool_types(&self, scope: &Scope<'a>) -> FnvHashSet> { + if let Ok(NamedEntities::Overloaded(overloaded)) = + scope.lookup(&Designator::OperatorSymbol(Operator::QueQue)) + { + overloaded + .entities() + .filter_map(|ent| ent.formals().nth(0).map(|typ| typ.type_mark().base())) + .collect() + } else { + FnvHashSet::default() + } + } + + /// An expression that is either boolean or implicitly boolean via ?? operator + pub fn boolean_expr( + &self, + scope: &Scope<'a>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + if let Some(types) = as_fatal(self.expr_type(scope, expr, diagnostics))? { + match types { + ExpressionType::Unambiguous(typ) => { + if typ.base() != self.boolean().base() { + let implicit_bools = self.implicit_bool_types(scope); + if !implicit_bools.contains(&typ.base()) { + diagnostics.add( + expr.pos(self.ctx), + format!( + "{} cannot be implicitly converted to {}. Operator ?? is not defined for this type.", + typ.describe(), + self.boolean().describe() + ), + ErrorCode::NoImplicitConversion, + ); + } + } + } + ExpressionType::Ambiguous(types) => { + if types.contains(&self.boolean().base()) { + self.expr_with_ttyp(scope, self.boolean(), expr, diagnostics)?; + } else { + let implicit_bool_types: FnvHashSet<_> = self + .implicit_bool_types(scope) + .intersection(&types) + .cloned() + .collect(); + + match implicit_bool_types.len().cmp(&1) { + std::cmp::Ordering::Equal => { + let typ: TypeEnt<'_> = types.into_iter().next().unwrap().into(); + self.expr_with_ttyp(scope, typ, expr, diagnostics)?; + } + std::cmp::Ordering::Greater => { + let mut diag = Diagnostic::new( + expr.pos(self.ctx), + "Ambiguous use of implicit boolean conversion ??", + ErrorCode::AmbiguousCall, + ); + diag.add_type_candididates("Could be", implicit_bool_types); + diagnostics.push(diag); + } + + std::cmp::Ordering::Less => { + let mut diag = Diagnostic::new( + expr.pos(self.ctx), + format!( + "Cannot disambiguate expression to {}", + self.boolean().describe() + ), + ErrorCode::AmbiguousExpression, + ); + diag.add_type_candididates( + "Implicit boolean conversion operator ?? is not defined for", + types, + ); + diagnostics.push(diag); + } + } + } + } + ExpressionType::String | ExpressionType::Null | ExpressionType::Aggregate => { + self.expr_with_ttyp(scope, self.boolean(), expr, diagnostics)?; + } + } + } + + Ok(()) + } + + /// Returns true if the name actually matches the target type + /// None if it was uncertain + pub fn expr_pos_with_ttyp( + &self, + scope: &Scope<'a>, + target_type: TypeEnt<'a>, + span: TokenSpan, + expr: &mut Expression, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let target_base = target_type.base_type(); + match expr { + Expression::Literal(ref mut lit) => { + self.analyze_literal_with_target_type(scope, target_type, span, lit, diagnostics)? + } + Expression::Name(ref mut name) => self.expression_name_with_ttyp( + scope, + span, + name.as_mut(), + target_type, + diagnostics, + )?, + Expression::Qualified(ref mut qexpr) => { + if let Some(type_mark) = + as_fatal(self.analyze_qualified_expression(scope, qexpr, diagnostics))? + { + if !self.can_be_target_type(type_mark, target_base.base()) { + diagnostics.push(Diagnostic::type_mismatch( + &span.pos(self.ctx), + &type_mark.describe(), + target_type, + )); + } + } + } + Expression::Binary(ref mut op, ref mut left, ref mut right) => { + let op_candidates = match as_fatal(self.lookup_operator( + diagnostics, + scope, + op.token, + op.item.item, + 2, + ))? { + Some(candidates) => candidates, + None => return Ok(()), + }; + + match as_fatal(self.disambiguate_op( + scope, + Some(target_type), + op, + op_candidates, + &mut [left.as_mut(), right.as_mut()], + diagnostics, + ))? { + Some(Disambiguated::Unambiguous(overloaded)) => { + let op_type = overloaded.return_type().unwrap(); + + if !self.can_be_target_type(op_type, target_type.base()) { + diagnostics.push(Diagnostic::type_mismatch( + &span.pos(self.ctx), + &op_type.describe(), + target_type, + )); + } + } + Some(Disambiguated::Ambiguous(candidates)) => { + diagnostics.push(Diagnostic::ambiguous_op( + op.pos(self.ctx), + op.item.item, + candidates, + )); + } + None => {} + } + } + Expression::Unary(ref mut op, ref mut expr) => { + let op_candidates = match as_fatal(self.lookup_operator( + diagnostics, + scope, + op.token, + op.item.item, + 1, + ))? { + Some(candidates) => candidates, + None => { + return Ok(()); + } + }; + + match as_fatal(self.disambiguate_op( + scope, + Some(target_type), + op, + op_candidates, + &mut [expr.as_mut()], + diagnostics, + ))? { + Some(Disambiguated::Unambiguous(overloaded)) => { + let op_type = overloaded.return_type().unwrap(); + + if !self.can_be_target_type(op_type, target_type.base()) { + diagnostics.push(Diagnostic::type_mismatch( + &span.pos(self.ctx), + &op_type.describe(), + target_type, + )); + } + } + Some(Disambiguated::Ambiguous(candidates)) => { + diagnostics.push(Diagnostic::ambiguous_op( + op.pos(self.ctx), + op.item.item, + candidates, + )); + } + None => {} + } + } + Expression::Aggregate(assocs) => match target_base.kind() { + Type::Array { + elem_type, indexes, .. + } => { + for assoc in assocs.iter_mut() { + as_fatal(self.array_assoc_elem( + scope, + target_base, + indexes, + *elem_type, + &mut assoc.item, + diagnostics, + ))?; + } + } + Type::Record(record_scope) => { + self.analyze_record_aggregate( + scope, + target_base, + record_scope, + span, + assocs, + diagnostics, + )?; + } + _ => { + self.analyze_aggregate(scope, assocs, diagnostics)?; + + diagnostics.add( + span.pos(self.ctx), + format!("composite does not match {}", target_type.describe()), + ErrorCode::TypeMismatch, + ); + } + }, + Expression::New(ref mut alloc) => { + self.analyze_allocation(scope, alloc, diagnostics)?; + } + Expression::Parenthesized(ref mut expr) => { + self.expr_pos_with_ttyp( + scope, + target_type, + expr.span, + &mut expr.item, + diagnostics, + )?; + } + } + + Ok(()) + } + + pub fn analyze_aggregate( + &self, + scope: &Scope<'a>, + assocs: &mut [WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for assoc in assocs.iter_mut() { + match &mut assoc.item { + ElementAssociation::Named(ref mut choices, ref mut expr) => { + for choice in choices.iter_mut() { + match choice.item { + Choice::Expression(..) => { + // @TODO could be record element so we cannot do more now + } + Choice::DiscreteRange(ref mut drange) => { + self.drange_unknown_type(scope, drange, diagnostics)?; + } + Choice::Others => {} + } + } + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + } + ElementAssociation::Positional(ref mut expr) => { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + } + } + } + Ok(()) + } + + pub fn analyze_record_aggregate( + &self, + scope: &Scope<'a>, + record_type: TypeEnt<'a>, + elems: &RecordRegion<'a>, + span: TokenSpan, + assocs: &mut [WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let mut associated = RecordAssociations::default(); + let mut is_ok_so_far = true; + + for (idx, assoc) in assocs.iter_mut().enumerate() { + match &mut assoc.item { + ElementAssociation::Named(ref mut choices, ref mut actual_expr) => { + let typ = if choices.len() == 1 { + let choice = choices.first_mut().unwrap(); + let choice_span = choice.span; + match &mut choice.item { + Choice::Expression(choice_expr) => { + if let Some(simple_name) = + as_name_mut(choice_expr).and_then(as_simple_name_mut) + { + if let Some(elem) = elems.lookup(&simple_name.item) { + simple_name.set_unique_reference(&elem); + associated.associate( + self.ctx, + &elem, + choice.span, + diagnostics, + ); + Some(elem.type_mark().base()) + } else { + is_ok_so_far = false; + diagnostics.push(Diagnostic::no_declaration_within( + &record_type, + &choice_span.pos(self.ctx), + &simple_name.item, + )); + None + } + } else { + is_ok_so_far = false; + diagnostics.add( + choice.pos(self.ctx), + "Record aggregate choice must be a simple name", + ErrorCode::MismatchedKinds, + ); + None + } + } + Choice::DiscreteRange(_) => { + is_ok_so_far = false; + diagnostics.add( + choice.pos(self.ctx), + "Record aggregate choice must be a simple name", + ErrorCode::MismatchedKinds, + ); + None + } + Choice::Others => { + // @TODO empty others + let remaining_types: FnvHashSet> = elems + .iter() + .filter_map(|elem| { + if !associated.is_associated(&elem) { + Some(elem.type_mark().base()) + } else { + None + } + }) + .collect(); + + if remaining_types.len() > 1 { + let mut diag = Diagnostic::new(choice.pos(self.ctx), format!("Other elements of record '{}' are not of the same type", record_type.designator()), ErrorCode::TypeMismatch); + for elem in elems.iter() { + if !associated.is_associated(&elem) { + if let Some(decl_pos) = elem.decl_pos() { + diag.add_related( + decl_pos, + format!( + "Element '{}' has {}", + elem.designator(), + elem.type_mark().describe() + ), + ); + } + } + } + diagnostics.push(diag); + } else if remaining_types.is_empty() { + diagnostics.push( + Diagnostic::new( + choice.pos(self.ctx), + format!( + "All elements of record '{}' are already associated", + record_type.designator() + ), + ErrorCode::AlreadyAssociated, + ) + .opt_related( + record_type.decl_pos(), + format!( + "Record '{}' defined here", + record_type.designator() + ), + ), + ) + } + + for elem in elems.iter() { + if !associated.is_associated(&elem) { + associated.associate( + self.ctx, + &elem, + choice.span, + diagnostics, + ); + } + } + + if remaining_types.len() == 1 { + remaining_types.into_iter().next() + } else { + None + } + } + } + } else { + if let (Some(first), Some(last)) = (choices.first(), choices.last()) { + is_ok_so_far = false; + let pos = first.span.combine(last.span); + diagnostics.add( + pos.pos(self.ctx), + "Record aggregate choice must be a simple name", + ErrorCode::MismatchedKinds, + ); + } + None + }; + + if let Some(typ) = typ { + self.expr_pos_with_ttyp( + scope, + typ.into(), + actual_expr.span, + &mut actual_expr.item, + diagnostics, + )?; + } else { + self.expr_unknown_ttyp(scope, actual_expr, diagnostics)?; + } + } + ElementAssociation::Positional(ref mut expr) => { + if let Some(elem) = elems.nth(idx) { + self.expr_with_ttyp(scope, elem.type_mark(), expr, diagnostics)?; + associated.associate(self.ctx, elem, expr.span, diagnostics); + } else { + self.expr_unknown_ttyp(scope, expr, diagnostics)?; + + diagnostics.push( + Diagnostic::new( + expr.pos(self.ctx), + format!( + "Unexpected positional association for record '{}'", + record_type.designator() + ), + ErrorCode::TooManyArguments, + ) + .opt_related( + record_type.decl_pos(), + format!("Record '{}' defined here", record_type.designator()), + ), + ) + } + } + } + } + + if is_ok_so_far { + // Do not complain about these when there are worse problems + for elem in elems.iter() { + if !associated.is_associated(&elem) { + diagnostics.push( + Diagnostic::new( + span.pos(self.ctx), + format!( + "Missing association of record element '{}'", + elem.designator() + ), + ErrorCode::Unassociated, + ) + .opt_related( + elem.decl_pos(), + format!("Record element '{}' defined here", elem.designator()), + ), + ) + } + } + } + + Ok(()) + } + + pub fn array_assoc_elem( + &self, + scope: &Scope<'a>, + array_type: TypeEnt<'a>, + index_types: &[Option>], + elem_type: TypeEnt<'a>, + assoc: &mut ElementAssociation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult { + let index_type = index_types.first().and_then(|x| *x); + let mut can_be_array = true; + + let expr = match assoc { + ElementAssociation::Named(ref mut choices, ref mut expr) => { + for choice in choices.iter_mut() { + match &mut choice.item { + Choice::Expression(index_expr) => { + match self.expr_as_discrete_range_type( + scope, + choice.span, + index_expr, + diagnostics, + )? { + Some(typ) => { + if let Some(index_type) = index_type { + if !self.can_be_target_type(typ, index_type) { + diagnostics.push(Diagnostic::type_mismatch( + &choice.pos(self.ctx), + &typ.describe(), + index_type.into(), + )); + } + } + + can_be_array = true; + } + None => { + if let Some(index_type) = index_type { + self.expr_pos_with_ttyp( + scope, + index_type.into(), + choice.span, + index_expr, + diagnostics, + )?; + } + can_be_array = false; + } + } + } + Choice::DiscreteRange(ref mut drange) => { + if let Some(index_type) = index_type { + self.drange_with_ttyp( + scope, + index_type.into(), + drange, + diagnostics, + )?; + } else { + self.drange_unknown_type(scope, drange, diagnostics)?; + } + } + Choice::Others => { + // @TODO choice must be alone so cannot appear here + can_be_array = false; + } + } + } + expr + } + ElementAssociation::Positional(ref mut expr) => expr, + }; + + if index_types.len() > 1 { + if let Expression::Aggregate(ref mut inner) = expr.item { + for assoc in inner.iter_mut() { + as_fatal(self.array_assoc_elem( + scope, + array_type, + &index_types[1..], + elem_type, + &mut assoc.item, + diagnostics, + ))?; + } + } else { + diagnostics.add( + expr.pos(self.ctx), + format!( + "Expected sub-aggregate for target {}", + array_type.describe() + ), + ErrorCode::ExpectedSubAggregate, + ); + } + } else if can_be_array { + // If the choice is only a range or positional the expression can be an array + let types = self.expr_type(scope, expr, diagnostics)?; + let is_array = self.is_possible(&types, array_type.base()); + let is_elem = self.is_possible(&types, elem_type.base()); + + if is_elem || !is_array { + // Prefer element type in presence of ambiguity + self.expr_pos_with_ttyp(scope, elem_type, expr.span, &mut expr.item, diagnostics)?; + } else if is_array { + self.expr_pos_with_ttyp(scope, array_type, expr.span, &mut expr.item, diagnostics)?; + } + } else { + self.expr_pos_with_ttyp(scope, elem_type, expr.span, &mut expr.item, diagnostics)?; + } + + Ok(()) + } +} + +impl Diagnostic { + fn ambiguous_op<'a>( + pos: &SrcPos, + op: Operator, + candidates: impl IntoIterator>, + ) -> Diagnostic { + let mut diag = Diagnostic::new( + pos, + format!( + "ambiguous use of {}", + Designator::OperatorSymbol(op).describe() + ), + ErrorCode::AmbiguousCall, + ); + diag.add_subprogram_candidates("might be", candidates); + diag + } +} + +#[derive(Default)] +struct RecordAssociations(FnvHashMap); + +impl RecordAssociations { + fn associate( + &mut self, + ctx: &dyn TokenAccess, + elem: &RecordElement<'_>, + pos: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) { + if let Some(prev_pos) = self.0.insert(elem.id(), pos) { + diagnostics.push( + Diagnostic::new( + pos.pos(ctx), + format!( + "Record element '{}' has already been associated", + elem.designator() + ), + ErrorCode::AlreadyAssociated, + ) + .related(prev_pos.pos(ctx), "Previously associated here"), + ); + } + } + + fn is_associated(&self, elem: &RecordElement<'_>) -> bool { + self.0.contains_key(&elem.id()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::analysis::tests::TestSetup; + use crate::data::DiagnosticHandler; + use crate::syntax::test::check_diagnostics; + use crate::syntax::test::without_related; + use crate::syntax::test::Code; + + impl<'a> TestSetup<'a> { + fn expr_type( + &'a self, + code: &Code, + diagnostics: &mut dyn DiagnosticHandler, + ) -> Option> { + let mut expr = code.expr(); + as_fatal( + self.ctx(&code.tokenize()) + .expr_type(&self.scope, &mut expr, diagnostics), + ) + .unwrap() + } + + fn expr_with_ttyp( + &'a self, + code: &Code, + ttyp: TypeEnt<'a>, + diagnostics: &mut dyn DiagnosticHandler, + ) { + let mut expr = code.expr(); + self.ctx(&code.tokenize()) + .expr_pos_with_ttyp(&self.scope, ttyp, expr.span, &mut expr.item, diagnostics) + .unwrap() + } + } + + #[test] + fn null_literal_expr_type() { + let test = TestSetup::new(); + assert_eq!( + test.expr_type(&test.snippet("null"), &mut NoDiagnostics), + Some(ExpressionType::Null) + ); + } + + #[test] + fn string_literal_expr_type() { + let test = TestSetup::new(); + assert_eq!( + test.expr_type(&test.snippet("\"hello\""), &mut NoDiagnostics), + Some(ExpressionType::String) + ); + + assert_eq!( + test.expr_type(&test.snippet("x\"hello\""), &mut NoDiagnostics), + Some(ExpressionType::String) + ); + } + + #[test] + fn character_literal_expr_type() { + let test = TestSetup::new(); + assert_eq!( + test.expr_type(&test.snippet("'a'"), &mut NoDiagnostics), + Some(ExpressionType::Unambiguous(test.lookup_type("character"))) + ); + } + + #[test] + fn character_literal_ambiguous_expr_type() { + let test = TestSetup::new(); + test.declarative_part("type enum_t is ('a', 'b');"); + assert_eq!( + test.expr_type(&test.snippet("'a'"), &mut NoDiagnostics), + Some(ExpressionType::Ambiguous( + [ + test.lookup_type("character").base(), + test.lookup_type("enum_t").base() + ] + .into_iter() + .collect() + )) + ); + } + + #[test] + fn universal_integer_expr_type() { + let test = TestSetup::new(); + assert_eq!( + test.expr_type(&test.snippet("0"), &mut NoDiagnostics), + Some(ExpressionType::Unambiguous( + test.ctx(&Vec::new()).universal_integer().into() + )) + ); + } + + #[test] + fn universal_real_expr_type() { + let test = TestSetup::new(); + assert_eq!( + test.expr_type(&test.snippet("0.0"), &mut NoDiagnostics), + Some(ExpressionType::Unambiguous( + test.ctx(&Vec::new()).universal_real().into() + )) + ); + } + + #[test] + fn physical_literal_type() { + let test = TestSetup::new(); + assert_eq!( + test.expr_type(&test.snippet("1 ns"), &mut NoDiagnostics), + Some(ExpressionType::Unambiguous(test.lookup_type("time"))) + ); + } + + #[test] + fn qualified_allocator() { + let test = TestSetup::new(); + test.declarative_part("type ptr_t is access integer_vector;"); + + assert_eq!( + test.expr_type( + &test.snippet("new integer_vector'(0, 1)"), + &mut NoDiagnostics + ), + Some(ExpressionType::Unambiguous( + test.lookup_type("integer_vector") + )) + ); + + // @TODO check type inside subtype indication + assert_eq!( + test.expr_type( + &test.snippet("new integer_vector(0 to 1)"), + &mut NoDiagnostics + ), + Some(ExpressionType::Unambiguous( + test.lookup_type("integer_vector") + )) + ); + } + + #[test] + fn binary_expression_types() { + let test = TestSetup::new(); + + let code = test.snippet("true and false"); + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Unambiguous(test.lookup_type("boolean"))) + ); + } + + #[test] + fn binary_expression_typecheck_error() { + let test = TestSetup::new(); + + let code = test.snippet("0 and 1"); + let mut diagnostics = Vec::new(); + + assert_eq!(test.expr_type(&code, &mut diagnostics), None); + + check_diagnostics( + without_related(&diagnostics), + vec![Diagnostic::new( + code.s1("and"), + "Found no match for operator \"and\"", + ErrorCode::Unresolved, + )], + ); + } + + #[test] + fn type_attributes_cannot_be_used_as_an_expression() { + let test = TestSetup::new(); + test.declarative_part("variable x : integer;"); + let code = test.snippet("x'subtype"); + + let mut diagnostics = Vec::new(); + assert_eq!(test.expr_type(&code, &mut diagnostics), None); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("x'subtype"), + "integer type 'INTEGER' cannot be used in an expression", + ErrorCode::MismatchedKinds, + )], + ); + } + + #[test] + fn binary_expression_missing_names() { + let test = TestSetup::new(); + + let code = test.snippet("0 + missing"); + let mut diagnostics = Vec::new(); + + assert_eq!(test.expr_type(&code, &mut diagnostics), None); + + check_diagnostics( + without_related(&diagnostics), + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); + } + + #[test] + fn expression_ambiguous_name() { + let test = TestSetup::new(); + test.declarative_part("type enum1_t is (alpha, beta);"); + test.declarative_part("type enum2_t is (alpha, beta);"); + + let code = test.snippet("alpha"); + + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Ambiguous( + [ + test.lookup_type("enum1_t").base(), + test.lookup_type("enum2_t").base() + ] + .into_iter() + .collect() + )) + ); + } + + #[test] + fn ambiguous_operator() { + let test = TestSetup::new(); + test.declarative_part( + " +function \"-\"(arg : bit_vector) return real; +function \"-\"(arg : string) return integer; + ", + ); + + let code = test.snippet("- \"01\""); + + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Ambiguous( + [ + test.lookup_type("integer").base(), + test.lookup_type("real").base(), + ] + .into_iter() + .collect() + )) + ); + } + + #[test] + fn ambiguous_argument_when_return_types_are_all_the_same() { + let test = TestSetup::new(); + let decls = test.declarative_part( + " + +function \"-\"(arg : bit_vector) return integer; +function \"-\"(arg : string) return integer; + ", + ); + + let code = test.snippet("- \"01\""); + let mut diagnostics = Vec::new(); + test.expr_with_ttyp(&code, test.lookup_type("INTEGER"), &mut diagnostics); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("-"), + "ambiguous use of operator \"-\"", + ErrorCode::AmbiguousCall, + ) + .related( + decls.s("\"-\"", 1), + "might be operator \"-\"[BIT_VECTOR return INTEGER]", + ) + .related( + decls.s("\"-\"", 2), + "might be operator \"-\"[STRING return INTEGER]", + )], + ); + } + + #[test] + fn string_cannot_match_multi_dimensional_array() { + let test = TestSetup::new(); + test.declarative_part( + " +type arr_t is array (natural range <>) of string(1 to 3); +constant c0 : arr_t(0 to 0) := (0 => \"abc\"); + ", + ); + + let code = test.snippet("\"123\" & c0"); + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Unambiguous(test.lookup_type("arr_t"))) + ); + } + + #[test] + fn aggregate_can_match_record() { + let test = TestSetup::new(); + test.declarative_part( + " +type rec_t is record + f0: natural; + f1: natural; +end record; +constant c0 : rec_t := (0, 1); + ", + ); + + let code = test.snippet("c0 = (0, 1)"); + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Unambiguous(test.lookup_type("boolean"))) + ); + } + + #[test] + fn does_not_remove_universal_candidates_when_return_types_differ() { + let test = TestSetup::new(); + test.declarative_part( + " +function \"+\"(a : integer; b : character) return character; +function \"+\"(a : integer; b : character) return integer; + ", + ); + + let code = test.snippet("0 + 'c'"); + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Ambiguous( + vec![ + test.lookup_type("integer").base(), + test.lookup_type("character").base() + ] + .into_iter() + .collect() + )) + ); + } + + #[test] + fn universal_expression_is_not_ambiguous() { + let test = TestSetup::new(); + let code = test.snippet("-1"); + assert_eq!( + test.expr_type(&code, &mut NoDiagnostics), + Some(ExpressionType::Unambiguous( + test.ctx(&code.tokenize()).universal_integer().into() + )) + ); + } + + #[test] + fn universal_integer_target_type_accepts_integer() { + let test = TestSetup::new(); + test.declarative_part( + " +function no_arg return boolean; +function no_arg return integer; +function with_arg(arg : natural) return boolean; +function with_arg(arg : natural) return integer; + + ", + ); + + let code = test.snippet("no_arg"); + test.expr_with_ttyp( + &code, + test.ctx(&code.tokenize()).universal_integer().into(), + &mut NoDiagnostics, + ); + let code = test.snippet("with_arg(0)"); + test.expr_with_ttyp( + &code, + test.ctx(&code.tokenize()).universal_integer().into(), + &mut NoDiagnostics, + ); + } +} diff --git a/vhdl_lang/src/analysis/literals.rs b/vhdl_lang/src/analysis/literals.rs new file mode 100644 index 0000000..64ad6e5 --- /dev/null +++ b/vhdl_lang/src/analysis/literals.rs @@ -0,0 +1,228 @@ +use fnv::FnvHashSet; + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use super::analyze::*; +use super::scope::*; +use crate::analysis::static_expression::{bit_string_to_string, BitStringConversionError}; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use crate::TokenSpan; + +impl<'a, 't> AnalyzeContext<'a, 't> { + /// Analyze a string literal or expanded bit-string literal for type-matching + fn analyze_string_literal( + &self, + span: TokenSpan, + string_lit: Latin1String, + target_base: TypeEnt<'_>, + target_type: TypeEnt<'_>, + diagnostics: &mut dyn DiagnosticHandler, + ) { + if let Some((elem_type, literals)) = as_single_index_enum_array(target_base) { + for chr in string_lit.chars() { + let chr = Designator::Character(*chr); + if !literals.contains(&chr) { + diagnostics.add( + span.pos(self.ctx), + format!("{} does not define character {}", elem_type.describe(), chr), + ErrorCode::InvalidLiteral, + ); + break; + } + } + } else { + diagnostics.add( + span.pos(self.ctx), + format!("string literal does not match {}", target_type.describe()), + ErrorCode::TypeMismatch, + ); + } + } + + /// Returns true if the name actually matches the target type + /// None if it was uncertain + pub fn analyze_literal_with_target_type( + &self, + scope: &Scope<'a>, + target_type: TypeEnt<'a>, + span: TokenSpan, + literal: &mut Literal, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let target_base = target_type.base_type(); + + match literal { + Literal::AbstractLiteral(abst) => match abst { + AbstractLiteral::Integer(_) => { + if !self.can_be_target_type(self.universal_integer().into(), target_type.base()) + { + diagnostics.add( + span.pos(self.ctx), + format!("integer literal does not match {}", target_type.describe()), + ErrorCode::TypeMismatch, + ); + } + } + AbstractLiteral::Real(_) => { + if !self.can_be_target_type(self.universal_real().into(), target_type.base()) { + diagnostics.add( + span.pos(self.ctx), + format!("real literal does not match {}", target_type.describe()), + ErrorCode::TypeMismatch, + ); + } + } + }, + Literal::Character(char) => match target_base.kind() { + Type::Enum(literals) => { + if !literals.contains(&Designator::Character(*char)) { + diagnostics.add( + span.pos(self.ctx), + format!( + "character literal does not match {}", + target_type.describe() + ), + ErrorCode::TypeMismatch, + ); + } + } + _ => { + diagnostics.add( + span.pos(self.ctx), + format!( + "character literal does not match {}", + target_type.describe() + ), + ErrorCode::TypeMismatch, + ); + } + }, + Literal::String(string_lit) => { + self.analyze_string_literal( + span, + string_lit.to_owned(), + target_base, + target_type, + diagnostics, + ); + } + Literal::BitString(bit_string) => { + match bit_string_to_string(bit_string) { + Ok(string_lit) => self.analyze_string_literal( + span, + string_lit, + target_base, + target_type, + diagnostics, + ), + Err(err) => { + match err { + BitStringConversionError::IllegalDecimalCharacter(rel_pos) => { + diagnostics.add( + span.pos(self.ctx), + format!( + "Illegal digit '{}' for base 10", + bit_string.value.bytes[rel_pos] as char, + ), + ErrorCode::InvalidLiteral, + ) + } + BitStringConversionError::IllegalTruncate(_, _) => { + diagnostics.add( + span.pos(self.ctx), + format!( + "Truncating vector to length {} would lose information", + bit_string.length.unwrap() // Safe as this error can only happen when there is a length + ), + ErrorCode::InvalidLiteral, + ); + } + BitStringConversionError::EmptySignedExpansion => { + diagnostics.add( + span.pos(self.ctx), + "Cannot expand an empty signed bit string", + ErrorCode::InvalidLiteral, + ); + } + } + } + } + } + Literal::Physical(PhysicalLiteral { ref mut unit, .. }) => { + match self.resolve_physical_unit(scope, unit) { + Ok(physical_type) => { + if physical_type.base_type() != target_base { + diagnostics.push(Diagnostic::type_mismatch( + &span.pos(self.ctx), + &physical_type.describe(), + target_type, + )) + } + } + Err(diagnostic) => { + diagnostics.push(diagnostic); + } + } + } + Literal::Null => { + if !matches!(target_base.kind(), Type::Access(_)) { + diagnostics.add( + span.pos(self.ctx), + format!("null literal does not match {}", target_base.describe()), + ErrorCode::TypeMismatch, + ); + } + } + }; + Ok(()) + } + + pub fn resolve_physical_unit( + &self, + scope: &Scope<'a>, + unit: &mut WithRef, + ) -> Result, Diagnostic> { + match scope + .lookup(&Designator::Identifier(unit.item.item.clone())) + .map_err(|err| err.into_diagnostic(self.ctx, unit.item.token))? + { + NamedEntities::Single(unit_ent) => { + unit.set_unique_reference(unit_ent); + if let AnyEntKind::PhysicalLiteral(physical_ent) = unit_ent.actual_kind() { + Ok(*physical_ent) + } else { + Err(Diagnostic::new( + unit.item.pos(self.ctx), + format!("{} is not a physical unit", unit_ent.describe()), + ErrorCode::InvalidLiteral, + )) + } + } + NamedEntities::Overloaded(_) => Err(Diagnostic::mismatched_kinds( + unit.item.pos(self.ctx), + "Overloaded name may not be physical unit", + )), + } + } +} + +/// Must be an array type with a single index of enum type +fn as_single_index_enum_array(typ: TypeEnt<'_>) -> Option<(TypeEnt<'_>, &FnvHashSet)> { + if let Type::Array { + indexes, elem_type, .. + } = typ.kind() + { + if indexes.len() == 1 { + if let Type::Enum(literals) = elem_type.base_type().kind() { + return Some((*elem_type, literals)); + } + } + } + None +} diff --git a/vhdl_lang/src/analysis/lock.rs b/vhdl_lang/src/analysis/lock.rs new file mode 100644 index 0000000..f13036e --- /dev/null +++ b/vhdl_lang/src/analysis/lock.rs @@ -0,0 +1,204 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +//! This module contains types to handle the analysis data in a thread-safe way, +//! in particular when the dependencies between design units are not known. + +use parking_lot::{MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +/// Combines an item to be analyzed (typically, a design unit) with the optional results +/// of that analysis. +struct AnalysisState { + /// Data gathered during analysis; `None` while not yet analyzed. + result: Option, + + /// The subject of analysis; typically, this is a design unit. + data: T, +} + +/// A thread-safe r/w-lock on an item to be analyzed (`T`) and the analysis results (`R`). +/// +/// Ensures mutable access during analysis and immutable access after analysis. +pub struct AnalysisLock { + state: RwLock>, +} + +impl AnalysisLock { + pub fn new(data: T) -> AnalysisLock { + AnalysisLock { + state: RwLock::new(AnalysisState { result: None, data }), + } + } + + /// Returns an immutable reference to the data and result if it has already been analyzed. + pub fn get(&self) -> Option> { + let guard = self.state.read(); + if guard.result.is_some() { + Some(ReadGuard { guard }) + } else { + None + } + } + + // Has been analyzed + pub fn is_analyzed(&self) -> bool { + self.get().is_some() + } + + /// Returns an mutable reference to the data. + pub fn write(&self) -> MappedRwLockWriteGuard<'_, T> { + RwLockWriteGuard::map(self.state.write(), |data| &mut data.data) + } + + /// Reset analysis state, analysis needs to be redone. + pub fn reset(&self) { + let mut guard = self.state.write(); + guard.result = None; + } + + /// Returns an immmutable reference to the data and result. + /// + /// Panics if the analysis result is not available. + pub fn expect_analyzed(&self) -> ReadGuard<'_, T, R> { + let guard = self.state.read(); + + if guard.result.is_none() { + panic!("Expected analysis to have already been done"); + } + + ReadGuard { guard } + } + + /// Creates a view into this lock. + /// + /// This view provides: + /// - a mutable reference to the data if not analyzed + /// - an immmutable reference to the data if already analyzed + pub fn entry(&self) -> AnalysisEntry<'_, T, R> { + if let Some(guard) = self.get() { + AnalysisEntry::Occupied(guard) + } else { + let guard = self.state.write(); + + if guard.result.is_some() { + let guard = ReadGuard { + guard: RwLockWriteGuard::downgrade(guard), + }; + + AnalysisEntry::Occupied(guard) + } else { + let guard = WriteGuard { guard }; + AnalysisEntry::Vacant(guard) + } + } + } +} + +/// A view into a thread-safe r/w-lock on an [`AnalysisState`](struct.AnalysisState.html). +/// +/// Instances of this type are created by +/// [`AnalysisLock::entry()`](struct.AnalysisLock.html#method.entry) +/// and allow read-access to a completed analysis data and +/// read/write-access to incomplete analysis data. +pub enum AnalysisEntry<'a, T, R> { + Occupied(ReadGuard<'a, T, R>), + Vacant(WriteGuard<'a, T, R>), +} + +pub struct ReadGuard<'a, T, R> { + guard: RwLockReadGuard<'a, AnalysisState>, +} + +impl<'a, T, R> ReadGuard<'a, T, R> { + pub fn result(&self) -> &R { + self.guard.result.as_ref().unwrap() + } + + pub fn data(&self) -> &T { + &self.guard.data + } +} + +impl<'a, T, R> std::ops::Deref for ReadGuard<'a, T, R> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard.data + } +} + +pub struct WriteGuard<'a, T, R> { + guard: RwLockWriteGuard<'a, AnalysisState>, +} + +impl<'a, T, R> WriteGuard<'a, T, R> { + pub fn finish(&mut self, result: R) { + self.guard.result = Some(result); + } + pub fn downgrade(self) -> ReadGuard<'a, T, R> { + if self.guard.result.is_none() { + panic!("Cannot downgrade unit without result"); + } + ReadGuard { + guard: RwLockWriteGuard::downgrade(self.guard), + } + } +} + +impl<'a, T, R> std::ops::Deref for WriteGuard<'a, T, R> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.guard.data + } +} + +impl<'a, T, R> std::ops::DerefMut for WriteGuard<'a, T, R> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.guard.data + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn analysis_result_is_memoized() { + let lock = AnalysisLock::new(1); + + match lock.entry() { + AnalysisEntry::Vacant(mut entry) => { + *entry = 2; + entry.finish(1.0); + } + _ => panic!("Expected Vacant entry"), + }; + + match lock.entry() { + AnalysisEntry::Occupied(entry) => { + assert_eq!(*entry, 2); + assert!((*entry.result() - 1.0_f64).abs() < f64::EPSILON); + } + _ => panic!("Expected Occupied entry"), + }; + + assert_eq!(*lock.get().unwrap(), 2); + assert!((*lock.get().unwrap().result() - 1.0_f64).abs() < f64::EPSILON); + + // Check that lock is reset + lock.reset(); + assert!(lock.get().is_none()); + + match lock.entry() { + AnalysisEntry::Vacant(mut entry) => { + *entry = 2; + entry.finish(1.0); + } + _ => panic!("Expected Vacant entry"), + }; + } +} diff --git a/vhdl_lang/src/analysis/names.rs b/vhdl_lang/src/analysis/names.rs new file mode 100644 index 0000000..e633b3e --- /dev/null +++ b/vhdl_lang/src/analysis/names.rs @@ -0,0 +1,3427 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com + +use crate::analysis::target::AssignmentType; +use fnv::FnvHashSet; +use vhdl_lang::{TokenAccess, TokenSpan}; + +use super::analyze::*; +use super::expression::ExpressionType; +use super::overloaded::Disambiguated; +use super::overloaded::DisambiguatedType; +use super::overloaded::SubprogramKind; +use super::scope::*; +use crate::ast::token_range::{WithToken, WithTokenSpan}; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ObjectBase<'a> { + Object(ObjectEnt<'a>), + ObjectAlias(ObjectEnt<'a>, EntRef<'a>), + DeferredConstant(EntRef<'a>), + ExternalName(ExternalObjectClass), +} + +impl<'a> ObjectBase<'a> { + pub fn mode(&self) -> Option<&InterfaceMode<'a>> { + use ObjectBase::*; + match self { + Object(object) | ObjectAlias(object, _) => object.mode(), + DeferredConstant(..) | ExternalName(_) => None, + } + } + + pub fn class(&self) -> ObjectClass { + match self { + ObjectBase::Object(object) => object.class(), + ObjectBase::ObjectAlias(object, _) => object.class(), + ObjectBase::DeferredConstant(..) => ObjectClass::Constant, + ObjectBase::ExternalName(class) => (*class).into(), + } + } + + /// Check that this object is a writable object and not constant or input only + pub fn can_be_assigned_to(&self) -> bool { + if self.class() == ObjectClass::Constant { + return false; + } + match self.mode() { + None => true, + Some(InterfaceMode::Simple(Mode::In)) => false, + Some(InterfaceMode::Simple(_)) => true, + // This is triggered when assigning, e.g., + // using foo.bar <= baz where `foo` is a view. + // TODO: check, that this assignment is legal. + Some(InterfaceMode::View(_)) => true, + } + } + + /// Check that a signal is not the target of a variable assignment and vice-versa + pub fn is_valid_assignment_type(&self, assignment_type: AssignmentType) -> bool { + let class = self.class(); + match assignment_type { + AssignmentType::Signal => matches!(class, ObjectClass::Signal), + AssignmentType::Variable => { + matches!(class, ObjectClass::Variable | ObjectClass::SharedVariable) + } + } + } + + // Use whenever the class and mode is relevant to the error + pub fn describe_class(&self) -> String { + if let Some(mode) = self.mode() { + if self.class() == ObjectClass::Constant { + format!("interface {}", self.describe()) + } else { + format!("interface {} of mode {}", self.describe(), mode) + } + } else { + self.describe() + } + } + + pub fn describe(&self) -> String { + match self { + ObjectBase::DeferredConstant(ent) => { + format!("deferred constant '{}'", ent.designator()) + } + ObjectBase::ExternalName(..) => "external name".to_owned(), + ObjectBase::Object(obj) => obj.describe_name(), + ObjectBase::ObjectAlias(_, alias) => { + format!("alias '{}' of {}", alias.designator(), self.class()) + } + } + } + + pub fn is_port(&self) -> bool { + use ObjectBase::*; + match self { + Object(obj) | ObjectAlias(obj, _) => obj.kind().is_port(), + DeferredConstant(_) | ExternalName(_) => false, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ObjectName<'a> { + pub base: ObjectBase<'a>, + pub type_mark: Option>, +} + +impl<'a> ObjectName<'a> { + pub fn type_mark(&self) -> TypeEnt<'a> { + if let Some(type_mark) = self.type_mark { + type_mark + } else if let ObjectBase::Object(obj) = self.base { + obj.type_mark() + } else { + unreachable!("No type mark implies object base") + } + } + + fn with_suffix(self, type_mark: TypeEnt<'a>) -> Self { + ObjectName { + base: self.base, + type_mark: Some(type_mark), + } + } + + /// Use in error messages that focus on the type rather than class/mode + pub fn describe_type(&self) -> String { + if let Some(type_mark) = self.type_mark { + type_mark.describe() + } else { + format!( + "{} of {}", + self.base.describe(), + self.type_mark().describe() + ) + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ResolvedName<'a> { + Library(Symbol), + Design(DesignEnt<'a>), + Type(TypeEnt<'a>), + Overloaded(WithToken, OverloadedName<'a>), + ObjectName(ObjectName<'a>), + /// The result of a function call and any subsequent selections thereof + Expression(DisambiguatedType<'a>), + // Something that cannot be further selected + Final(EntRef<'a>), +} + +impl<'a> ResolvedName<'a> { + /// The name was selected out of a design unit + fn from_design_not_overloaded(ent: &'a AnyEnt<'_>) -> Result { + let name = match ent.kind() { + AnyEntKind::Object(_) => ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(ObjectEnt::from_any(ent).unwrap()), + type_mark: None, + }), + AnyEntKind::ObjectAlias { + base_object, + type_mark, + } => ResolvedName::ObjectName(ObjectName { + base: ObjectBase::ObjectAlias(*base_object, ent), + type_mark: Some(type_mark.to_owned()), + }), + AnyEntKind::ExternalAlias { class, type_mark } => { + ResolvedName::ObjectName(ObjectName { + base: ObjectBase::ExternalName(*class), + type_mark: Some(*type_mark), + }) + } + AnyEntKind::DeferredConstant(subtype) => ResolvedName::ObjectName(ObjectName { + base: ObjectBase::DeferredConstant(ent), + type_mark: Some(subtype.type_mark()), + }), + AnyEntKind::Type(_) => ResolvedName::Type(TypeEnt::from_any(ent).unwrap()), + AnyEntKind::View(_) => ResolvedName::Final(ent), + AnyEntKind::Overloaded(_) => { + return Err(( + "Internal error. Unreachable as overloaded is handled outside".to_owned(), + ErrorCode::Internal, + )) + } + AnyEntKind::File(_) + | AnyEntKind::InterfaceFile(_) + | AnyEntKind::Component(_) + | AnyEntKind::PhysicalLiteral(_) => ResolvedName::Final(ent), + AnyEntKind::Design(_) => ResolvedName::Design( + DesignEnt::from_any(ent).expect("AnyEntKind::Design is not a design entity"), + ), + AnyEntKind::Library + | AnyEntKind::Attribute(_) + | AnyEntKind::ElementDeclaration(_) + | AnyEntKind::Concurrent(_) + | AnyEntKind::Sequential(_) + | AnyEntKind::LoopParameter(_) => { + return Err(( + format!( + "{} cannot be selected from design unit", + ent.kind().describe() + ), + ErrorCode::MismatchedKinds, + )); + } + }; + + Ok(name) + } + + /// The name was looked up from the current scope + fn from_scope_not_overloaded(ent: &'a AnyEnt<'_>) -> Result { + let name = match ent.kind() { + AnyEntKind::Object(_) => ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(ObjectEnt::from_any(ent).unwrap()), + type_mark: None, + }), + AnyEntKind::ObjectAlias { + base_object, + type_mark, + } => ResolvedName::ObjectName(ObjectName { + base: ObjectBase::ObjectAlias(*base_object, ent), + type_mark: Some(type_mark.to_owned()), + }), + AnyEntKind::ExternalAlias { class, type_mark } => { + ResolvedName::ObjectName(ObjectName { + base: ObjectBase::ExternalName(*class), + type_mark: Some(*type_mark), + }) + } + AnyEntKind::DeferredConstant(subtype) => ResolvedName::ObjectName(ObjectName { + base: ObjectBase::DeferredConstant(ent), + type_mark: Some(subtype.type_mark()), + }), + AnyEntKind::Type(_) => ResolvedName::Type(TypeEnt::from_any(ent).unwrap()), + AnyEntKind::Design(_) => ResolvedName::Design(DesignEnt::from_any(ent).unwrap()), + AnyEntKind::Library => { + ResolvedName::Library(ent.designator().as_identifier().cloned().unwrap()) + } + AnyEntKind::Overloaded(_) => { + return Err(( + "Internal error. Unreachable as overloaded is handled outside this function" + .to_string(), + ErrorCode::Internal, + )); + } + AnyEntKind::File(_) + | AnyEntKind::View(_) + | AnyEntKind::InterfaceFile(_) + | AnyEntKind::Component(_) + | AnyEntKind::Concurrent(_) + | AnyEntKind::Sequential(_) + | AnyEntKind::LoopParameter(_) + | AnyEntKind::PhysicalLiteral(_) => ResolvedName::Final(ent), + AnyEntKind::Attribute(_) | AnyEntKind::ElementDeclaration(_) => { + return Err(( + format!( + "{} should never be looked up from the current scope", + ent.kind().describe() + ), + ErrorCode::Internal, + )); + } + }; + + Ok(name) + } + + /// A description that includes the type of the name + /// This is used in contexts where the type is relevant to the error + pub fn describe_type(&self) -> String { + match self { + ResolvedName::ObjectName(oname) => oname.describe_type(), + ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)) => { + format!("Expression of {}", typ.describe()) + } + _ => self.describe(), + } + } + + /// A description that does not include the name of the type + /// This is used in contexts where the type is not relevant + /// Such as when assigning to a constant + pub fn describe(&self) -> String { + match self { + ResolvedName::Library(sym) => format!("library {sym}"), + ResolvedName::Design(ent) => ent.describe(), + ResolvedName::Type(ent) => ent.describe(), + ResolvedName::Overloaded(des, name) => { + if let Some(ent) = name.as_unique() { + ent.describe() + } else { + format!("Overloaded name {des}") + } + } + ResolvedName::ObjectName(oname) => oname.base.describe(), + ResolvedName::Final(ent) => ent.describe(), + ResolvedName::Expression(DisambiguatedType::Unambiguous(_)) => "Expression".to_owned(), + ResolvedName::Expression(_) => "Ambiguous expression".to_owned(), + } + } + + pub fn decl_pos(&self) -> Option<&SrcPos> { + match self { + ResolvedName::Library(_) => None, + ResolvedName::Design(design) => design.decl_pos(), + ResolvedName::Type(typ) => typ.decl_pos(), + ResolvedName::Overloaded(_, names) => names.as_unique().and_then(|it| it.decl_pos()), + ResolvedName::ObjectName(name) => match name.base { + ObjectBase::Object(ent) => ent.decl_pos(), + ObjectBase::DeferredConstant(ent) | ObjectBase::ObjectAlias(_, ent) => { + ent.decl_pos() + } + ObjectBase::ExternalName(_) => None, + }, + ResolvedName::Expression(_) | ResolvedName::Final(_) => None, + } + } + + fn type_mark(&self) -> Option> { + match self { + ResolvedName::Type(typ) => Some(*typ), + ResolvedName::ObjectName(oname) => Some(oname.type_mark()), + ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)) => Some(*typ), + _ => None, + } + } + + pub(crate) fn as_type_of_attr_prefix( + &self, + ctx: &dyn TokenAccess, + prefix_pos: TokenSpan, + attr: &AttributeSuffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + if let Some(typ) = self.type_mark() { + Ok(typ) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &prefix_pos.pos(ctx), + self, + attr, + )); + Err(EvalError::Unknown) + } + } + + fn as_type_of_signal_attr_prefix( + &self, + ctx: &dyn TokenAccess, + prefix_pos: TokenSpan, + attr: &AttributeSuffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + if let ResolvedName::ObjectName(oname) = self { + if matches!(oname.base.class(), ObjectClass::Signal) { + return Ok(*oname); + } + } + + diagnostics.add( + prefix_pos.pos(ctx), + format!( + "Expected signal prefix for '{} attribute, got {}", + attr.attr, + self.describe() + ), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + + // The actual underlying entity + fn as_actual_entity(&self) -> Option> { + match self { + ResolvedName::ObjectName(oname) => match oname.base { + ObjectBase::Object(obj) => Some(*obj), + ObjectBase::ObjectAlias(obj, _) => Some(*obj), + ObjectBase::DeferredConstant(ent) => Some(ent), + ObjectBase::ExternalName(_) => None, + }, + ResolvedName::Type(typ) => Some((*typ).into()), + ResolvedName::Design(des) => Some((*des).into()), + ResolvedName::Library(..) => None, + ResolvedName::Overloaded(_, _) => None, + ResolvedName::Expression(_) => None, + ResolvedName::Final(_) => None, + } + } +} + +#[derive(Debug)] +pub struct AttributeSuffix<'a> { + pub attr: &'a mut WithToken, + pub expr: &'a mut Option>>, +} + +#[derive(Debug)] +enum Suffix<'a> { + Selected(&'a mut WithToken>), + All, + Slice(&'a mut DiscreteRange), + Attribute(AttributeSuffix<'a>), + CallOrIndexed(&'a mut [AssociationElement]), +} + +enum SplitName<'a> { + Designator(&'a mut WithRef), + External(&'a mut ExternalName), + Suffix(&'a mut WithTokenSpan, Suffix<'a>), +} + +impl<'a> SplitName<'a> { + fn from_name(name: &'a mut Name) -> SplitName<'a> { + match name { + Name::Designator(d) => SplitName::Designator(d), + Name::External(e) => SplitName::External(e), + Name::Selected(prefix, suffix) => { + SplitName::Suffix(prefix.as_mut(), Suffix::Selected(suffix)) + } + Name::SelectedAll(ref mut prefix) => SplitName::Suffix(prefix.as_mut(), Suffix::All), + Name::Slice(ref mut prefix, range) => { + SplitName::Suffix(prefix.as_mut(), Suffix::Slice(range)) + } + Name::Attribute(ref mut attr) => SplitName::Suffix( + &mut attr.name, + Suffix::Attribute(AttributeSuffix { + attr: &mut attr.attr, + expr: &mut attr.expr, + }), + ), + Name::CallOrIndexed(ref mut fcall) => SplitName::Suffix( + &mut fcall.name, + Suffix::CallOrIndexed(&mut fcall.parameters.items), + ), + } + } +} + +enum TypeOrMethod<'a> { + Type(TypeEnt<'a>), + Method(WithToken, OverloadedName<'a>), +} + +fn could_be_indexed_name(assocs: &[AssociationElement]) -> bool { + assocs + .iter() + .all(|assoc| assoc.formal.is_none() && !matches!(assoc.actual.item, ActualPart::Open)) +} + +pub fn as_type_conversion( + assocs: &mut [AssociationElement], +) -> Option<(TokenSpan, &mut Expression)> { + if assocs.len() == 1 && could_be_indexed_name(assocs) { + if let ActualPart::Expression(ref mut expr) = assocs[0].actual.item { + return Some((assocs[0].actual.span, expr)); + } + } + None +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + fn name_to_type( + &self, + pos: TokenSpan, + // Reference to set if overloaded name was disambiguated + reference: Option<&mut Reference>, + name: ResolvedName<'a>, + ) -> Result>, Diagnostic> { + match name { + ResolvedName::Library(_) | ResolvedName::Design(_) | ResolvedName::Type(_) => { + Err(Diagnostic::mismatched_kinds( + pos.pos(self.ctx), + format!("{} cannot be used in an expression", name.describe_type()), + )) + } + ResolvedName::Final(ent) => match ent.actual_kind() { + AnyEntKind::LoopParameter(typ) => { + Ok(typ.map(|typ| DisambiguatedType::Unambiguous(typ.into()))) + } + AnyEntKind::PhysicalLiteral(typ) => Ok(Some(DisambiguatedType::Unambiguous(*typ))), + AnyEntKind::File(subtype) => { + Ok(Some(DisambiguatedType::Unambiguous(subtype.type_mark()))) + } + AnyEntKind::InterfaceFile(typ) => Ok(Some(DisambiguatedType::Unambiguous(*typ))), + _ => Err(Diagnostic::mismatched_kinds( + pos.pos(self.ctx), + format!("{} cannot be used in an expression", name.describe_type()), + )), + }, + ResolvedName::Overloaded(des, overloaded) => { + if let Some(disamb) = self.disambiguate_no_actuals(&des, None, &overloaded)? { + if let Disambiguated::Unambiguous(ref ent) = disamb { + if let Some(reference) = reference { + reference.set(ent.id()); + } + } + Ok(Some(disamb.into_type())) + } else { + Ok(None) + } + } + ResolvedName::ObjectName(oname) => { + Ok(Some(DisambiguatedType::Unambiguous(oname.type_mark()))) + } + ResolvedName::Expression(expr_type) => Ok(Some(expr_type)), + } + } + + fn name_to_unambiguous_type( + &self, + span: TokenSpan, + name: &ResolvedName<'a>, + ttyp: TypeEnt<'a>, + // Optional reference to set when disambiguating overloaded + suffix_ref: Option<&mut Reference>, + ) -> Result>, Diagnostic> { + match name { + ResolvedName::Library(_) | ResolvedName::Design(_) | ResolvedName::Type(_) => { + Err(Diagnostic::mismatched_kinds( + span.pos(self.ctx), + format!("{} cannot be used in an expression", name.describe_type()), + )) + } + ResolvedName::Final(ent) => match ent.actual_kind() { + AnyEntKind::LoopParameter(typ) => Ok(typ.map(|typ| typ.into())), + AnyEntKind::PhysicalLiteral(typ) => Ok(Some(*typ)), + AnyEntKind::File(subtype) => Ok(Some(subtype.type_mark())), + AnyEntKind::InterfaceFile(typ) => Ok(Some(*typ)), + _ => Err(Diagnostic::mismatched_kinds( + span.pos(self.ctx), + format!("{} cannot be used in an expression", name.describe_type()), + )), + }, + ResolvedName::Overloaded(des, overloaded) => { + if let Some(disamb) = self.disambiguate_no_actuals(des, Some(ttyp), overloaded)? { + match disamb { + Disambiguated::Unambiguous(ent) => { + if let Some(reference) = suffix_ref { + reference.set(ent.id()); + } + Ok(Some(ent.return_type().unwrap())) + } + Disambiguated::Ambiguous(overloaded) => { + Err(Diagnostic::ambiguous_call(self.ctx, des, overloaded)) + } + } + } else { + Ok(None) + } + } + ResolvedName::ObjectName(oname) => Ok(Some(oname.type_mark())), + ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)) => Ok(Some(*typ)), + ResolvedName::Expression(DisambiguatedType::Ambiguous(_)) => { + // Error reported elsewhere + Ok(None) + } + } + } + + /// An array type may be sliced with a type name + /// For the parser this looks like a call or indexed name + /// Example: + /// subtype sub_t is natural range 0 to 1; + /// arr(sub_t) := (others => 0); + fn assoc_as_discrete_range_type( + &self, + scope: &Scope<'a>, + assocs: &mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + if !could_be_indexed_name(assocs) { + return Ok(None); + } + + if let [ref mut assoc] = assocs { + if let ActualPart::Expression(expr) = &mut assoc.actual.item { + return self.expr_as_discrete_range_type( + scope, + assoc.actual.span, + expr, + diagnostics, + ); + } + } + Ok(None) + } + + pub fn expr_as_discrete_range_type( + &self, + scope: &Scope<'a>, + expr_pos: TokenSpan, + expr: &mut Expression, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + if let Expression::Name(name) = expr { + if let Name::Attribute(ref mut attr) = name.as_mut() { + if attr.as_range().is_some() { + return if let Some(typ) = + as_fatal(self.range_attribute_type(scope, attr.as_mut(), diagnostics))? + { + Ok(Some(typ.into())) + } else { + Ok(None) + }; + } + } + + if !name.is_selected_name() { + // Early exit + return Ok(None); + } + + let resolved = as_fatal(self.name_resolve(scope, expr_pos, name, diagnostics))?; + + if let Some(ResolvedName::Type(typ)) = resolved { + return if matches!(typ.base_type().kind(), Type::Enum { .. } | Type::Integer) { + Ok(Some(typ)) + } else { + bail!( + diagnostics, + Diagnostic::mismatched_kinds( + expr_pos.pos(self.ctx), + format!("{} cannot be used as a discrete range", typ.describe()), + ) + ); + }; + } + } + + Ok(None) + } + + // Apply suffix when prefix is known to have a type + // The prefix may be an object or a function return value + fn resolve_typed_suffix( + &self, + scope: &Scope<'a>, + prefix_pos: TokenSpan, + name_pos: TokenSpan, + prefix_typ: TypeEnt<'a>, + suffix: &mut Suffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + match suffix { + Suffix::Selected(suffix) => Ok(Some( + match prefix_typ + .selected(self.ctx, prefix_pos, suffix) + .into_eval_result(diagnostics)? + { + TypedSelection::RecordElement(elem) => { + suffix.set_unique_reference(&elem); + TypeOrMethod::Type(elem.type_mark()) + } + TypedSelection::ProtectedMethod(name) => TypeOrMethod::Method( + WithToken::new(suffix.item.item.clone(), suffix.token), + name, + ), + }, + )), + Suffix::All => Ok(prefix_typ.accessed_type().map(TypeOrMethod::Type)), + Suffix::Slice(drange) => Ok(if let Some(typ) = prefix_typ.sliced_as() { + if let Type::Array { indexes, .. } = typ.kind() { + if let [idx_typ] = indexes.as_slice() { + if let Some(idx_typ) = *idx_typ { + self.drange_with_ttyp(scope, idx_typ.into(), drange, diagnostics)?; + } else { + self.drange_unknown_type(scope, drange, diagnostics)?; + } + } else { + diagnostics.add( + name_pos.pos(self.ctx), + format!( + "Cannot slice {}-dimensional {}", + indexes.len(), + typ.describe() + ), + ErrorCode::MismatchedKinds, + ) + } + } + Some(TypeOrMethod::Type(typ)) + } else { + None + }), + // @TODO attribute is handled elesewhere + Suffix::Attribute(_) => Ok(None), + // @TODO Prefix must non-overloaded + Suffix::CallOrIndexed(assocs) => { + if let Some(typ) = prefix_typ.sliced_as() { + if self + .assoc_as_discrete_range_type(scope, assocs, diagnostics)? + .is_some() + { + return Ok(Some(TypeOrMethod::Type(typ))); + } + } + + if could_be_indexed_name(assocs) { + if let Some((elem_type, indexes)) = prefix_typ.array_type() { + for (idx, AssociationElement { actual, .. }) in + assocs.iter_mut().enumerate() + { + if let ActualPart::Expression(ref mut expr) = actual.item { + if let Some(ttyp) = indexes.get(idx) { + if let Some(ttyp) = *ttyp { + self.expr_pos_with_ttyp( + scope, + ttyp.into(), + actual.span, + expr, + diagnostics, + )?; + } else { + self.expr_pos_unknown_ttyp( + scope, + actual.span, + expr, + diagnostics, + )?; + } + } + } + } + + let num_indexes = indexes.len(); + if assocs.len() != num_indexes { + bail!( + diagnostics, + Diagnostic::dimension_mismatch( + &name_pos.pos(self.ctx), + prefix_typ, + assocs.len(), + num_indexes, + ) + ); + } else { + Ok(Some(TypeOrMethod::Type(elem_type))) + } + } else { + Ok(None) + } + } else { + Ok(None) + } + } + } + } + + // Resolve an index used in an array attribute such as arr_t'left(0) to an index type + pub(crate) fn array_index_expression_in_attribute( + &self, + indexes: &[Option>], + mut expr: Option<&mut WithTokenSpan>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let idx = if let Some(expr) = expr.as_mut() { + if let Expression::Literal(Literal::AbstractLiteral(AbstractLiteral::Integer(idx))) = + expr.item + { + idx as usize + } else { + diagnostics.add( + expr.span.pos(self.ctx), + "Expected an integer literal", + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + } else { + 1 + }; + + if let Some(idx_typ) = indexes.get(idx - 1) { + if let Some(idx_typ) = idx_typ { + Ok(*idx_typ) + } else { + // Array index was not analyzed + Err(EvalError::Unknown) + } + } else { + if let Some(expr) = expr { + let ndims = indexes.len(); + let dimensions = plural("dimension", "dimensions", ndims); + diagnostics.add(expr.pos(self.ctx), format!("Index {idx} out of range for array with {ndims} {dimensions}, expected 1 to {ndims}"), ErrorCode::DimensionMismatch); + } + Err(EvalError::Unknown) + } + } + + pub(crate) fn resolve_view_ent( + &self, + resolved: &ResolvedName<'a>, + diagnostics: &mut dyn DiagnosticHandler, + pos: TokenSpan, + ) -> EvalResult> { + let ent = match resolved { + ResolvedName::Final(ent) => { + let Some(view_ent) = ViewEnt::from_any(ent) else { + bail!( + diagnostics, + Diagnostic::new( + pos.pos(self.ctx), + format!("{} is not a view", resolved.describe()), + ErrorCode::MismatchedKinds + ) + ); + }; + view_ent + } + _ => { + bail!( + diagnostics, + Diagnostic::new( + pos.pos(self.ctx), + format!("{} is not a view", resolved.describe()), + ErrorCode::MismatchedKinds + ) + ); + } + }; + Ok(ent) + } + + pub fn attribute_suffix( + &self, + name_pos: TokenSpan, + prefix_pos: TokenSpan, + scope: &Scope<'a>, + prefix: &ResolvedName<'a>, + attr: &mut AttributeSuffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match attr.attr.item { + AttributeDesignator::Left + | AttributeDesignator::Right + | AttributeDesignator::High + | AttributeDesignator::Low => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if let Some((_, indexes)) = typ.array_type() { + self.array_index_expression_in_attribute( + indexes, + attr.expr.as_mut().map(|expr| expr.as_mut()), + diagnostics, + ) + .map(|typ| ResolvedName::Expression(DisambiguatedType::Unambiguous(typ.into()))) + } else if typ.is_scalar() { + check_no_attr_argument(self.ctx, attr, diagnostics); + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + typ, + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Ascending => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if typ.array_type().is_some() { + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + self.boolean(), + ))) + } else if typ.is_scalar() { + check_no_attr_argument(self.ctx, attr, diagnostics); + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + self.boolean(), + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Image => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if let Some(ref mut expr) = + check_single_argument(self.ctx, name_pos, attr, diagnostics) + { + self.expr_with_ttyp(scope, typ, expr, diagnostics)?; + } + + if typ.is_scalar() { + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + self.string(), + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Value => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if let Some(ref mut expr) = + check_single_argument(self.ctx, name_pos, attr, diagnostics) + { + self.expr_with_ttyp(scope, self.string(), expr, diagnostics)?; + } + + if typ.is_scalar() { + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + typ, + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Pos => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if typ.base().is_discrete() { + if let Some(ref mut expr) = + check_single_argument(self.ctx, name_pos, attr, diagnostics) + { + self.expr_with_ttyp(scope, typ, expr, diagnostics)?; + } + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + self.universal_integer().into(), + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Val => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if typ.base().is_discrete() { + if let Some(ref mut expr) = + check_single_argument(self.ctx, name_pos, attr, diagnostics) + { + self.expr_with_ttyp( + scope, + self.universal_integer().into(), + expr, + diagnostics, + )?; + } + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + typ, + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Succ + | AttributeDesignator::Pred + | AttributeDesignator::LeftOf + | AttributeDesignator::RightOf => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if typ.base().is_discrete() { + if let Some(ref mut expr) = + check_single_argument(self.ctx, name_pos, attr, diagnostics) + { + self.expr_with_ttyp(scope, typ, expr, diagnostics)?; + } + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + typ, + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Length => { + let typ = prefix.as_type_of_attr_prefix(self.ctx, prefix_pos, attr, diagnostics)?; + + if typ.array_type().is_some() { + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + self.universal_integer().into(), + ))) + } else { + diagnostics.push(Diagnostic::cannot_be_prefix_of_attribute( + &name_pos.pos(self.ctx), + prefix, + attr, + )); + Err(EvalError::Unknown) + } + } + AttributeDesignator::SimpleName + | AttributeDesignator::InstanceName + | AttributeDesignator::PathName => { + check_no_attr_argument(self.ctx, attr, diagnostics); + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + self.string(), + ))) + } + + AttributeDesignator::Signal(sattr) => self.signal_attribute_suffix( + scope, + prefix, + name_pos, + prefix_pos, + sattr, + attr, + diagnostics, + ), + + AttributeDesignator::Ident(ref mut sym) => { + if let Some(actual) = prefix.as_actual_entity() { + if let Some(attr) = actual.get_attribute(&sym.item) { + sym.set_unique_reference(attr.into()); + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + attr.typ(), + ))) + } else { + diagnostics.add( + attr.attr.pos(self.ctx), + format!("Unknown attribute '{}", attr.attr.item), + ErrorCode::Unresolved, + ); + Err(EvalError::Unknown) + } + } else { + diagnostics.add( + name_pos.pos(self.ctx), + format!( + "{} may not be the prefix of a user defined attribute", + prefix.describe() + ), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } + AttributeDesignator::Range(_) => { + diagnostics.add( + name_pos.pos(self.ctx), + "Range cannot be used as an expression", + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + AttributeDesignator::Type(attr) => self + .resolve_type_attribute_suffix(prefix, prefix_pos, &attr, name_pos, diagnostics) + .map(|typ| ResolvedName::Type(typ.base().into())), + AttributeDesignator::Converse => { + let view = self.resolve_view_ent(prefix, diagnostics, prefix_pos)?; + // Since we do not check the actual mode of the view, + // this does not actually converse anything at the moment. + // We only check that the selected name is a view and return that view. + Ok(ResolvedName::Final(view.ent)) + } + } + } + + #[allow(clippy::too_many_arguments)] + fn signal_attribute_suffix( + &self, + scope: &Scope<'a>, + prefix: &ResolvedName<'a>, + name_pos: TokenSpan, + prefix_pos: TokenSpan, + attr: SignalAttribute, + suffix: &mut AttributeSuffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + use SignalAttribute::*; + let object_name = + prefix.as_type_of_signal_attr_prefix(self.ctx, prefix_pos, suffix, diagnostics)?; + let object_ent = match object_name.base { + ObjectBase::Object(obj_ent) | ObjectBase::ObjectAlias(obj_ent, _) => obj_ent, + // - DeferredConstant: cannot happen here + // - ExternalName: No analysis performed currently. Simply ignore. + ObjectBase::DeferredConstant(_) | ObjectBase::ExternalName(_) => { + return Err(EvalError::Unknown) + } + }; + let expr = suffix.expr.as_mut().map(|expr| expr.as_mut()); + let typ = match attr { + Delayed => { + if let Some(expr) = expr { + self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; + } + object_name.type_mark() + } + Stable | Quiet => { + if let Some(expr) = expr { + self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; + } + self.boolean() + } + Event | Active | Driving => { + check_no_sattr_argument(self.ctx, attr, expr, diagnostics); + self.boolean() + } + Transaction => { + check_no_sattr_argument(self.ctx, attr, expr, diagnostics); + self.bit() + } + LastEvent | LastActive => { + check_no_sattr_argument(self.ctx, attr, expr, diagnostics); + self.time() + } + LastValue | DrivingValue => { + check_no_sattr_argument(self.ctx, attr, expr, diagnostics); + object_name.type_mark() + } + }; + let obj = self.arena.alloc( + Designator::Identifier(self.root.symbol_utf8(&suffix.attr.item.to_string())), + object_ent.parent, + Related::DerivedFrom(object_ent.ent), + AnyEntKind::Object(Object { + class: ObjectClass::Signal, + iface: None, + subtype: Subtype::new(typ), + has_default: false, + }), + None, + name_pos, + Some(self.source()), + ); + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(ObjectEnt::from_any(obj).unwrap()), + type_mark: Some(typ), + })) + } + + /// Resolves any type attribute suffixes + /// + /// # Example + /// ```vhdl + /// variable x: std_logic_vector(2 downto 0); + /// x'subtype -> std_logic_vector(2 downto 0) + /// x'element -> std_logic + /// ``` + fn resolve_type_attribute_suffix( + &self, + prefix: &ResolvedName<'a>, + prefix_pos: TokenSpan, + suffix: &TypeAttribute, + pos: TokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let typ = match prefix { + ResolvedName::Type(typ) => { + if *suffix == TypeAttribute::Element { + *typ + } else { + let diag = Diagnostic::illegal_attribute( + pos.pos(self.ctx), + format!( + "The {suffix} attribute can only be used on objects, not {}", + typ.describe() + ), + ) + .opt_related(typ.decl_pos(), "Defined here"); + bail!(diagnostics, diag); + } + } + ResolvedName::ObjectName(obj) => obj.type_mark(), + other => { + let diag = Diagnostic::mismatched_kinds( + pos.pos(self.ctx), + format!("Expected type, got {}", other.describe()), + ) + .opt_related(other.decl_pos(), "Defined here"); + bail!(diagnostics, diag); + } + }; + + match suffix { + TypeAttribute::Subtype => Ok(typ), + TypeAttribute::Element => { + if let Some((elem_type, _)) = typ.array_type() { + Ok(elem_type) + } else { + let diag = Diagnostic::illegal_attribute( + prefix_pos.pos(self.ctx), + format!("array type expected for '{suffix} attribute"), + ); + bail!(diagnostics, diag); + } + } + } + } + + pub fn name_resolve( + &self, + scope: &Scope<'a>, + name_pos: TokenSpan, + name: &mut Name, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + self.name_resolve_with_suffixes(scope, name_pos, name, None, false, diagnostics) + } + + fn name_resolve_with_suffixes( + &self, + scope: &Scope<'a>, + span: TokenSpan, + name: &mut Name, + ttyp: Option>, + has_suffix: bool, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let mut suffix; + let prefix; + let mut resolved = match SplitName::from_name(name) { + SplitName::Designator(designator) => { + let name = scope + .lookup(designator.designator()) + .map_err(|err| err.into_diagnostic(self.ctx, span)) + .into_eval_result(diagnostics)?; + return Ok(match name { + NamedEntities::Single(ent) => { + designator.set_unique_reference(ent); + + ResolvedName::from_scope_not_overloaded(ent) + .map_err(|(e, code)| Diagnostic::new(span.pos(self.ctx), e, code)) + .into_eval_result(diagnostics)? + } + NamedEntities::Overloaded(overloaded) => ResolvedName::Overloaded( + WithToken::new(designator.designator().clone(), span.start_token), + overloaded, + ), + }); + } + SplitName::External(ename) => { + let ExternalName { subtype, class, .. } = ename; + let subtype = self.resolve_subtype_indication(scope, subtype, diagnostics)?; + return Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::ExternalName(*class), + type_mark: Some(subtype.type_mark().to_owned()), + })); + } + SplitName::Suffix(p, s) => { + let resolved = self.name_resolve_with_suffixes( + scope, + p.span, + &mut p.item, + None, + true, + diagnostics, + )?; + prefix = p; + suffix = s; + resolved + } + }; + + // Any other suffix must collapse overloaded + if !matches!(suffix, Suffix::CallOrIndexed(_)) { + if let ResolvedName::Overloaded(ref des, ref overloaded) = resolved { + let disambiguated = self + .disambiguate_no_actuals( + des, + { + // @TODO must be disambiguated with suffixes + None + }, + overloaded, + ) + .into_eval_result(diagnostics)?; + + if let Some(disambiguated) = disambiguated { + match disambiguated { + Disambiguated::Ambiguous(ents) => { + if let Some(types) = ambiguous_functions_to_types(&ents) { + if has_suffix || ttyp.is_some() { + diagnostics + .push(Diagnostic::ambiguous_call(self.ctx, des, ents)); + } + resolved = + ResolvedName::Expression(DisambiguatedType::Ambiguous(types)); + } else { + diagnostics.add( + prefix.pos(self.ctx), + "Procedure calls are not valid in names and expressions", + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + } + Disambiguated::Unambiguous(ent) => { + prefix.set_unique_reference(&ent); + + if let Some(typ) = ent.return_type() { + resolved = + ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)); + } else { + diagnostics.add( + prefix.pos(self.ctx), + "Procedure calls are not valid in names and expressions", + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + } + } + } + } + } + + if let Suffix::Attribute(ref mut attr) = suffix { + return self.attribute_suffix(span, prefix.span, scope, &resolved, attr, diagnostics); + } + + match resolved { + ResolvedName::Overloaded(ref des, ref overloaded) => { + if let Suffix::CallOrIndexed(ref mut assocs) = suffix { + // @TODO could be overloaded with no arguments that is indexed + + // @TODO lookup already set reference to get O(N) instead of O(N^2) when disambiguating deeply nested ambiguous calls + if let Some(id) = prefix.item.get_suffix_reference() { + if let Some(ent) = OverloadedEnt::from_any(self.arena.get(id)) { + return Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + ent.return_type().unwrap(), + ))); + } + } + + match as_fatal(self.disambiguate( + scope, + &span.pos(self.ctx), + des, + assocs, + SubprogramKind::Function(if has_suffix { + // @TODO disambiguate based on suffixes + None + } else { + ttyp + }), + overloaded.entities().collect(), + diagnostics, + ))? { + Some(Disambiguated::Ambiguous(ents)) => { + if let Some(types) = ambiguous_functions_to_types(&ents) { + if has_suffix || ttyp.is_some() { + diagnostics + .push(Diagnostic::ambiguous_call(self.ctx, des, ents)); + } + + resolved = + ResolvedName::Expression(DisambiguatedType::Ambiguous(types)); + } else { + diagnostics.add( + prefix.pos(self.ctx), + "Procedure calls are not valid in names and expressions", + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + } + Some(Disambiguated::Unambiguous(ent)) => { + prefix.set_unique_reference(&ent); + if let Some(return_type) = ent.return_type() { + resolved = ResolvedName::Expression( + DisambiguatedType::Unambiguous(return_type), + ); + } else { + diagnostics.add( + prefix.pos(self.ctx), + "Procedure calls are not valid in names and expressions", + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + } + None => { + return Err(EvalError::Unknown); + } + } + } else { + diagnostics.push(Diagnostic::unreachable( + &span.pos(self.ctx), + "CallOrIndexed should already be handled", + )); + return Err(EvalError::Unknown); + } + } + ResolvedName::ObjectName(oname) => { + match self.resolve_typed_suffix( + scope, + prefix.span, + span, + oname.type_mark(), + &mut suffix, + diagnostics, + )? { + Some(TypeOrMethod::Type(typ)) => { + resolved = ResolvedName::ObjectName(oname.with_suffix(typ)); + } + Some(TypeOrMethod::Method(des, name)) => { + resolved = ResolvedName::Overloaded(des, name); + } + None => { + diagnostics.push(Diagnostic::cannot_be_prefix( + &prefix.pos(self.ctx), + resolved, + suffix, + )); + return Err(EvalError::Unknown); + } + } + } + ResolvedName::Expression(ref typ) => match typ { + DisambiguatedType::Unambiguous(typ) => { + match self.resolve_typed_suffix( + scope, + prefix.span, + span, + *typ, + &mut suffix, + diagnostics, + )? { + Some(TypeOrMethod::Type(typ)) => { + resolved = + ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)); + } + Some(TypeOrMethod::Method(des, name)) => { + resolved = ResolvedName::Overloaded(des, name); + } + None => { + diagnostics.push(Diagnostic::cannot_be_prefix( + &prefix.pos(self.ctx), + resolved, + suffix, + )); + return Err(EvalError::Unknown); + } + } + } + DisambiguatedType::Ambiguous(_) => { + // @TODO ambiguous error + return Err(EvalError::Unknown); + } + }, + + ResolvedName::Library(ref library_name) => { + if let Suffix::Selected(ref mut designator) = suffix { + resolved = ResolvedName::Design( + self.lookup_in_library( + diagnostics, + library_name, + designator.pos(self.ctx), + &designator.item.item, + ) + .map(|design| { + designator + .item + .reference + .set_unique_reference(design.into()); + design + })?, + ); + } else { + diagnostics.push(Diagnostic::cannot_be_prefix( + &span.pos(self.ctx), + resolved, + suffix, + )); + return Err(EvalError::Unknown); + } + } + ResolvedName::Design(ref ent) => { + if let Suffix::Selected(ref mut designator) = suffix { + let name = ent + .selected(self.ctx, prefix.span, designator) + .into_eval_result(diagnostics)?; + resolved = match name { + NamedEntities::Single(named_entity) => { + designator.set_reference(&name); + + ResolvedName::from_design_not_overloaded(named_entity) + .map_err(|(e, code)| { + Diagnostic::new(designator.pos(self.ctx), e, code) + }) + .into_eval_result(diagnostics)? + } + NamedEntities::Overloaded(overloaded) => { + // Could be used for an alias of a subprogram + ResolvedName::Overloaded( + WithToken::new(designator.item.item.clone(), designator.token), + overloaded, + ) + } + } + } else { + diagnostics.push(Diagnostic::cannot_be_prefix( + &span.pos(self.ctx), + resolved, + suffix, + )); + return Err(EvalError::Unknown); + } + } + ResolvedName::Type(typ) => match suffix { + Suffix::Selected(selected) => { + let typed_selection = match typ.selected(self.ctx, prefix.span, selected) { + Ok(typed_selection) => typed_selection, + Err(diagnostic) => { + bail!(diagnostics, diagnostic); + } + }; + return Ok(match typed_selection { + TypedSelection::RecordElement(element) => { + ResolvedName::Type(element.type_mark()) + } + TypedSelection::ProtectedMethod(method) => ResolvedName::Overloaded( + selected.clone().map_into(|desi| desi.item), + method, + ), + }); + } + Suffix::CallOrIndexed(ref mut assocs) => { + if let Some((expr_pos, expr)) = as_type_conversion(assocs) { + self.check_type_conversion(scope, typ, expr_pos, expr, diagnostics)?; + return Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + typ, + ))); + } + } + _ => { + bail!( + diagnostics, + Diagnostic::cannot_be_prefix(&span.pos(self.ctx), resolved, suffix) + ); + } + }, + ResolvedName::Final(_) => { + diagnostics.push(Diagnostic::cannot_be_prefix( + &span.pos(self.ctx), + resolved, + suffix, + )); + return Err(EvalError::Unknown); + } + } + + Ok(resolved) + } + + // Helper function: + // Resolve a name that must be some kind of object selection, index or slice + // Such names occur as assignment targets and aliases + // Takes an error message as an argument to be re-usable + pub fn resolve_object_name( + &self, + scope: &Scope<'a>, + name_pos: TokenSpan, + name: &mut Name, + err_msg: &'static str, + error_code: ErrorCode, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let resolved = self.name_resolve(scope, name_pos, name, diagnostics)?; + match resolved { + ResolvedName::ObjectName(oname) => Ok(oname), + ResolvedName::Library(_) + | ResolvedName::Design(_) + | ResolvedName::Type(_) + | ResolvedName::Overloaded { .. } + | ResolvedName::Expression(_) + | ResolvedName::Final(_) => { + diagnostics.add( + name_pos.pos(self.ctx), + format!("{} {}", resolved.describe(), err_msg), + error_code, + ); + Err(EvalError::Unknown) + } + } + } + + pub fn type_name( + &self, + scope: &Scope<'a>, + name_pos: TokenSpan, + name: &mut Name, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let resolved = self.name_resolve(scope, name_pos, name, diagnostics)?; + match resolved { + ResolvedName::Type(typ) => Ok(typ), + ResolvedName::Library(_) + | ResolvedName::Design(_) + | ResolvedName::ObjectName(_) + | ResolvedName::Overloaded { .. } + | ResolvedName::Expression(_) + | ResolvedName::Final(_) => { + bail!( + diagnostics, + Diagnostic::mismatched_kinds( + name_pos.pos(self.ctx), + format!("Expected type, got {}", resolved.describe()) + ) + .opt_related(resolved.decl_pos(), "Defined here") + ); + } + } + } + + pub fn check_type_conversion( + &self, + scope: &Scope<'a>, + typ: TypeEnt<'a>, + pos: TokenSpan, + expr: &mut Expression, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + if let Some(types) = as_fatal(self.expr_pos_type(scope, pos, expr, diagnostics))? { + match types { + ExpressionType::Unambiguous(ctyp) => { + if !typ.base().is_closely_related(ctyp.base()) { + diagnostics.add( + pos.pos(self.ctx), + format!( + "{} cannot be converted to {}", + ctyp.describe(), + typ.describe() + ), + ErrorCode::TypeMismatch, + ) + } + } + ExpressionType::String + | ExpressionType::Ambiguous(_) + | ExpressionType::Null + | ExpressionType::Aggregate => diagnostics.add( + pos.pos(self.ctx), + format!( + "{} cannot be the argument of type conversion", + types.describe() + ), + ErrorCode::MismatchedKinds, + ), + } + } + Ok(()) + } + + /// Analyze a name that is part of an expression that could be ambiguous + pub fn expression_name_types( + &self, + scope: &Scope<'a>, + span: TokenSpan, + name: &mut Name, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let resolved = + self.name_resolve_with_suffixes(scope, span, name, None, false, diagnostics)?; + match self.name_to_type(span, name.suffix_reference_mut(), resolved) { + Ok(Some(typ)) => Ok(typ), + Ok(None) => Err(EvalError::Unknown), + Err(diag) => { + diagnostics.push(diag); + Err(EvalError::Unknown) + } + } + } + + /// Analyze a name that is part of an expression that must be unambiguous + pub fn expression_name_with_ttyp( + &self, + scope: &Scope<'a>, + span: TokenSpan, + name: &mut Name, + ttyp: TypeEnt<'a>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + if let Some(resolved) = as_fatal(self.name_resolve_with_suffixes( + scope, + span, + name, + Some(ttyp), + false, + diagnostics, + ))? { + // @TODO target_type already used above, functions could probably be simplified + match self.name_to_unambiguous_type(span, &resolved, ttyp, name.suffix_reference_mut()) + { + Ok(Some(type_mark)) => { + if !self.can_be_target_type(type_mark, ttyp.base()) { + diagnostics.push(Diagnostic::type_mismatch( + &span.pos(self.ctx), + &resolved.describe_type(), + ttyp, + )); + } + } + Ok(None) => {} + Err(diag) => { + diagnostics.push(diag); + } + } + } + Ok(()) + } + + /// Analyze an indexed name where the prefix entity is already known + /// Returns the type of the array element + pub fn analyze_indexed_name( + &self, + scope: &Scope<'a>, + name_pos: TokenSpan, + suffix_pos: TokenSpan, + type_mark: TypeEnt<'a>, + indexes: &mut [Index<'_>], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let base_type = type_mark.base_type(); + + let base_type = if let Type::Access(ref subtype, ..) = base_type.kind() { + subtype.base_type() + } else { + base_type + }; + + if let Type::Array { + indexes: ref index_types, + elem_type, + .. + } = base_type.kind() + { + if indexes.len() != index_types.len() { + diagnostics.push(Diagnostic::dimension_mismatch( + &name_pos.pos(self.ctx), + base_type, + indexes.len(), + index_types.len(), + )) + } + + for index in indexes.iter_mut() { + self.expr_pos_unknown_ttyp(scope, index.pos, index.expr, diagnostics)?; + } + + Ok(*elem_type) + } else { + bail!( + diagnostics, + Diagnostic::new( + suffix_pos.pos(self.ctx), + format!("{} cannot be indexed", type_mark.describe()), + ErrorCode::MismatchedKinds, + ) + ); + } + } + + pub fn lookup_selected( + &self, + diagnostics: &mut dyn DiagnosticHandler, + prefix_pos: TokenSpan, + prefix: EntRef<'a>, + suffix: &mut WithToken>, + ) -> EvalResult> { + match prefix.actual_kind() { + AnyEntKind::Library => { + let library_name = prefix.designator().expect_identifier(); + let named_entity = self.lookup_in_library( + diagnostics, + library_name, + suffix.pos(self.ctx), + &suffix.item.item, + )?; + suffix + .item + .reference + .set_unique_reference(named_entity.into()); + Ok(NamedEntities::new(named_entity.into())) + } + AnyEntKind::Object(ref object) => Ok(object + .subtype + .type_mark() + .selected(self.ctx, prefix_pos, suffix) + .into_eval_result(diagnostics)? + .into_any()), + AnyEntKind::ObjectAlias { ref type_mark, .. } => Ok(type_mark + .selected(self.ctx, prefix_pos, suffix) + .into_eval_result(diagnostics)? + .into_any()), + AnyEntKind::ExternalAlias { ref type_mark, .. } => Ok(type_mark + .selected(self.ctx, prefix_pos, suffix) + .into_eval_result(diagnostics)? + .into_any()), + AnyEntKind::ElementDeclaration(ref subtype) => Ok(subtype + .type_mark() + .selected(self.ctx, prefix_pos, suffix) + .into_eval_result(diagnostics)? + .into_any()), + AnyEntKind::Design(_) => { + let design = DesignEnt::from_any(prefix) + .ok_or_else(|| { + Diagnostic::internal( + suffix.pos(self.ctx), + format!( + "Internal error when expecting design unit, got {}", + prefix.describe() + ), + ) + }) + .into_eval_result(diagnostics)?; + + let named = design + .selected(self.ctx, prefix_pos, suffix) + .into_eval_result(diagnostics)?; + Ok(named) + } + + _ => { + bail!( + diagnostics, + Diagnostic::invalid_selected_name_prefix(prefix, &prefix_pos.pos(self.ctx)) + ); + } + } + } +} + +fn plural(singular: &'static str, plural: &'static str, count: usize) -> &'static str { + if count == 1 { + singular + } else { + plural + } +} + +impl Declaration { + pub fn describe(&self) -> &'static str { + match self { + Declaration::Object(ObjectDeclaration { class, .. }) => match class { + ObjectClass::Constant => "constant", + ObjectClass::Signal => "signal", + ObjectClass::Variable => "variable", + ObjectClass::SharedVariable => "shared variable", + }, + Declaration::File(_) => "file", + Declaration::Type(TypeDeclaration { def, .. }) => match def { + TypeDefinition::Subtype(_) => "subtype", + _ => "type", + }, + Declaration::Component(_) => "component", + Declaration::Attribute(attribute) => match attribute { + Attribute::Specification(_) => "attribute specification", + Attribute::Declaration(_) => "attribute", + }, + Declaration::Alias(_) => "alias", + Declaration::SubprogramDeclaration(_) => "subprogram", + Declaration::SubprogramInstantiation(_) => "subprogram instantiation", + Declaration::SubprogramBody(_) => "subprogram body", + Declaration::Use(_) => "use", + Declaration::Package(_) => "package instantiation", + Declaration::Configuration(_) => "configuration", + Declaration::View(_) => "view", + } + } +} + +impl Diagnostic { + fn cannot_be_prefix( + prefix_pos: &SrcPos, + resolved: ResolvedName<'_>, + suffix: Suffix<'_>, + ) -> Diagnostic { + let suffix_desc = match suffix { + Suffix::Selected(_) => "selected", + Suffix::All => "accessed with .all", + Suffix::Slice(_) => "sliced", + Suffix::Attribute(_) => "the prefix of an attribute", + Suffix::CallOrIndexed(ref assoc) => { + if could_be_indexed_name(assoc) { + "indexed" + } else { + "called as a function" + } + } + }; + + let (name_desc, error_code) = if matches!(suffix, Suffix::CallOrIndexed(ref assoc) if !could_be_indexed_name(assoc) ) + { + // When something cannot be called as a function the type is not relevant + (resolved.describe(), ErrorCode::InvalidCall) + } else { + (resolved.describe_type(), ErrorCode::MismatchedKinds) + }; + + Diagnostic::new( + prefix_pos, + format!("{name_desc} cannot be {suffix_desc}"), + error_code, + ) + } + + fn cannot_be_prefix_of_attribute( + prefix_pos: &SrcPos, + resolved: &ResolvedName<'_>, + attr: &AttributeSuffix<'_>, + ) -> Diagnostic { + Diagnostic::new( + prefix_pos, + format!( + "{} cannot be the the prefix of '{} attribute", + resolved.describe_type(), + attr.attr + ), + ErrorCode::CannotBePrefixed, + ) + } + + fn dimension_mismatch( + pos: &SrcPos, + base_type: TypeEnt<'_>, + got: usize, + expected: usize, + ) -> Diagnostic { + let mut diag = Diagnostic::new( + pos, + "Number of indexes does not match array dimension", + ErrorCode::DimensionMismatch, + ); + + if let Some(decl_pos) = base_type.decl_pos() { + diag.add_related( + decl_pos, + capitalize(&format!( + "{} has {} {}, got {} {}", + base_type.describe(), + expected, + plural("dimension", "dimensions", expected), + got, + plural("index", "indexes", got), + )), + ); + } + + diag + } + + /// An internal logic error that we want to show to the user to get bug reports + fn unreachable(pos: &SrcPos, expected: &str) -> Diagnostic { + Diagnostic::new( + pos, + format!("Internal error, unreachable code {expected}"), + ErrorCode::Internal, + ) + } + + pub fn ambiguous_call<'a>( + ctx: &dyn TokenAccess, + call_name: &WithToken, + candidates: impl IntoIterator>, + ) -> Diagnostic { + let mut diag = Diagnostic::new( + call_name.pos(ctx), + format!("Ambiguous call to {}", call_name.item.describe()), + ErrorCode::AmbiguousCall, + ); + diag.add_subprogram_candidates("Might be", candidates); + diag + } +} + +fn check_no_attr_argument( + ctx: &dyn TokenAccess, + suffix: &AttributeSuffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, +) { + if let Some(ref expr) = suffix.expr { + diagnostics.add( + expr.pos(ctx), + format!("'{} attribute does not take an argument", suffix.attr), + ErrorCode::TooManyArguments, + ) + } +} + +fn check_no_sattr_argument( + ctx: &dyn TokenAccess, + attr: SignalAttribute, + expr: Option<&mut WithTokenSpan>, + diagnostics: &mut dyn DiagnosticHandler, +) { + if let Some(ref expr) = expr { + diagnostics.add( + expr.pos(ctx), + format!("'{attr} attribute does not take an argument"), + ErrorCode::TooManyArguments, + ) + } +} + +fn check_single_argument<'a>( + ctx: &dyn TokenAccess, + pos: TokenSpan, + suffix: &'a mut AttributeSuffix<'_>, + diagnostics: &mut dyn DiagnosticHandler, +) -> Option<&'a mut WithTokenSpan> { + if let Some(ref mut expr) = suffix.expr { + Some(expr) + } else { + diagnostics.add( + pos.pos(ctx), + format!("'{} attribute requires a single argument", suffix.attr), + ErrorCode::Unassociated, + ); + None + } +} + +fn ambiguous_functions_to_types<'a>( + overloaded: &[OverloadedEnt<'a>], +) -> Option>> { + let types: FnvHashSet<_> = overloaded + .iter() + .filter_map(|ent| ent.return_type()) + .map(|typ| typ.base()) + .collect(); + + if !types.is_empty() { + Some(types) + } else { + None + } +} + +#[cfg(test)] +mod test { + use super::*; + use assert_matches::assert_matches; + + use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder, TestSetup}; + use crate::syntax::test::check_diagnostics; + use crate::syntax::test::Code; + + impl<'a> TestSetup<'a> { + fn name_resolve( + &'a self, + code: &Code, + ttyp: Option>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let mut name = code.name(); + self.ctx(&code.tokenize()).name_resolve_with_suffixes( + &self.scope, + name.span, + &mut name.item, + ttyp, + false, + diagnostics, + ) + } + + fn expression_name_with_ttyp( + &'a self, + code: &Code, + ttyp: TypeEnt<'a>, + diagnostics: &mut dyn DiagnosticHandler, + ) { + let mut name = code.name(); + self.ctx(&code.tokenize()) + .expression_name_with_ttyp( + &self.scope, + name.span, + &mut name.item, + ttyp, + diagnostics, + ) + .unwrap() + } + + fn expression_name_types( + &'a self, + code: &Code, + diagnostics: &mut dyn DiagnosticHandler, + ) -> Option> { + let mut name = code.name(); + as_fatal(self.ctx(&code.tokenize()).expression_name_types( + &self.scope, + name.span, + &mut name.item, + diagnostics, + )) + .unwrap() + } + } + + #[test] + fn consecutive_name_attributes() { + let test = TestSetup::new(); + test.declarative_part( + "\ +variable a: natural range 0 to 9 := 9; +variable b: natural range 0 to a'subtype'high; + ", + ); + assert_matches!( + test.name_resolve(&test.snippet("b"), None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(_)) + ); + } + + #[test] + fn element_subtype_for_non_arrays() { + let test = TestSetup::new(); + test.declarative_part( + " +variable thevar : integer; + ", + ); + let code = test.snippet("thevar'element"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::illegal_attribute( + code.s1("thevar"), + "array type expected for 'element attribute", + )], + ) + } + + #[test] + fn element_type_attributes_on_non_object_types() { + let test = TestSetup::new(); + let declarative_code = test.declarative_part( + " +type my_type is array(natural range<>) of integer; + ", + ); + let code = test.snippet("my_type'subtype"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::illegal_attribute( + code.s1("my_type'subtype"), + "The subtype attribute can only be used on objects, not array type 'my_type'", + ) + .related(declarative_code.s1("my_type"), "Defined here")], + ) + } + + #[test] + fn consecutive_type_attributes() { + let test = TestSetup::new(); + test.declarative_part( + " +variable x: integer; + ", + ); + let code = test.snippet("x'subtype'subtype'high"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + let integer_pos = test + .ctx(&Vec::new()) + .root + .find_standard_symbol("INTEGER") + .decl_pos() + .unwrap(); + check_diagnostics( + diagnostics, + vec![Diagnostic::illegal_attribute( + code.s1("x'subtype'subtype"), + "The subtype attribute can only be used on objects, not integer type 'INTEGER'", + ) + .related(integer_pos, "Defined here")], + ) + } + + #[test] + fn object_name() { + let test = TestSetup::new(); + test.declarative_part("constant c0 : natural := 0;"); + assert_matches!( + test.name_resolve(&test.snippet("c0"), None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(_)) + ); + } + + #[test] + fn selected_object_name() { + let test = TestSetup::new(); + test.declarative_part( + " +type rec_t is record + field : natural; +end record; +constant c0 : rec_t := (others => 0); +", + ); + + assert_matches!(test.name_resolve(&test.snippet("c0.field"), None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("natural")); + } + + #[test] + fn access_all() { + let test = TestSetup::new(); + test.declarative_part( + " +type ptr_t is access integer_vector; +variable vptr : ptr_t; +", + ); + let resolved = test.name_resolve(&test.snippet("vptr.all"), None, &mut NoDiagnostics); + assert_matches!(resolved, Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("integer_vector")); + } + + #[test] + fn indexed_name() { + let test = TestSetup::new(); + test.declarative_part( + " +variable c0 : integer_vector(0 to 1); +", + ); + let resolved = test.name_resolve(&test.snippet("c0(0)"), None, &mut NoDiagnostics); + assert_matches!(resolved, Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("integer")); + } + + #[test] + fn indexed_name_type() { + let test = TestSetup::new(); + test.declarative_part( + " +variable c0 : integer_vector(0 to 1); +", + ); + let code = test.snippet("c0('a')"); + let mut diagnostics = Vec::new(); + let resolved = test.name_resolve(&code, None, &mut diagnostics); + assert_matches!(resolved, Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("integer")); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("'a'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ) + } + + #[test] + fn indexed_name_cannot_be_call() { + let test = TestSetup::new(); + test.declarative_part( + " +variable c0 : integer_vector(0 to 1); +", + ); + let code = test.snippet("c0(open)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&test.snippet("c0(open)"), None, &mut diagnostics), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("c0"), + "variable 'c0' cannot be called as a function", + ErrorCode::InvalidCall, + )], + ); + } + + #[test] + fn overloaded_name() { + let test = TestSetup::new(); + assert_matches!( + test.name_resolve(&test.snippet("true"), None, &mut NoDiagnostics), + Ok(ResolvedName::Overloaded { .. }) + ); + } + + #[test] + fn call_result() { + let test = TestSetup::new(); + test.declarative_part( + " +function fun(arg: natural) return integer; + ", + ); + assert_eq!( + test.name_resolve(&test.snippet("fun(0)"), None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))), + ); + } + + #[test] + fn disambiguates_call_with_arguments_by_return_type() { + let test = TestSetup::new(); + test.declarative_part( + " +function fun(arg: natural) return integer; +function fun(arg: natural) return character; + ", + ); + test.expression_name_with_ttyp( + &test.snippet("fun(0)"), + test.lookup_type("integer"), + &mut NoDiagnostics, + ); + } + + #[test] + fn overloaded_name_can_be_selected() { + let test = TestSetup::new(); + test.declarative_part( + " +type rec_t is record + fld : natural; +end record; + +function foo return rec_t; +", + ); + test.expression_name_with_ttyp( + &test.snippet("foo.fld"), + test.lookup_type("natural"), + &mut NoDiagnostics, + ); + } + + #[test] + fn procedure_cannot_be_used() { + let test = TestSetup::new(); + test.declarative_part( + " +procedure proc(arg: natural); + ", + ); + let mut diagnostics = Vec::new(); + let code = test.snippet("proc(0)"); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("proc"), + "Procedure calls are not valid in names and expressions", + ErrorCode::MismatchedKinds, + )], + ); + } + + #[test] + fn file_can_be_expression() { + let test = TestSetup::new(); + test.declarative_part( + " +type file_t is file of character; +file myfile : file_t; +", + ); + let code = test.snippet("myfile"); + assert_eq!( + test.expression_name_types(&code, &mut NoDiagnostics), + Some(DisambiguatedType::Unambiguous(test.lookup_type("file_t"))), + ) + } + + #[test] + fn disambiguates_by_target_type() { + let test = TestSetup::new(); + test.declarative_part( + " +type enum1_t is (alpha, beta); +type enum2_t is (alpha, beta); +", + ); + let code = test.snippet("alpha"); + test.expression_name_with_ttyp(&code, test.lookup_type("enum2_t"), &mut NoDiagnostics); + } + + #[test] + fn fcall_result_can_be_sliced() { + let test = TestSetup::new(); + test.declarative_part( + " +function myfun(arg : integer) return string; +", + ); + let code = test.snippet("myfun(0)(0 to 1)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("string") + ))) + ); + } + + #[test] + fn fcall_without_actuals_can_be_sliced() { + let test = TestSetup::new(); + test.declarative_part( + " +function myfun return string; +", + ); + let code = test.snippet("myfun(0 to 1)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("string") + ))) + ); + } + + #[test] + fn disambiguates_with_target_type() { + let test = TestSetup::new(); + test.declarative_part( + " +type enum1_t is (alpha, beta); +type enum2_t is (alpha, beta); +", + ); + let code = test.snippet("alpha"); + test.expression_name_with_ttyp(&code, test.lookup_type("enum1_t"), &mut NoDiagnostics); + } + + #[test] + fn slice_access_type() { + let test = TestSetup::new(); + test.declarative_part( + " +type ptr_t is access integer_vector; +variable vptr : ptr_t; +", + ); + let code = test.snippet("vptr(0 to 1)"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("integer_vector") + ); + } + + #[test] + fn index_access_type() { + let test = TestSetup::new(); + test.declarative_part( + " +type ptr_t is access integer_vector; +variable vptr : ptr_t; +", + ); + let code = test.snippet("vptr(0)"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("integer") + ); + } + + #[test] + fn slice_with_integer_discrete_range() { + let test = TestSetup::new(); + test.declarative_part( + " +subtype sub_t is integer range 0 to 3; +variable c0 : integer_vector(0 to 6); +", + ); + let code = test.snippet("c0(sub_t)"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("integer_vector") + ); + } + + #[test] + fn slice_with_enum_discrete_range() { + let test = TestSetup::new(); + test.declarative_part( + " +type enum_t is (a, b, c); +type arr_t is array (enum_t) of character; +subtype sub_t is enum_t range a to b; +variable c0 : arr_t(a to c); +", + ); + let code = test.snippet("c0(sub_t)"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(oname)) if oname.type_mark() == test.lookup_type("arr_t") + ); + } + + #[test] + fn slice_with_bad_type() { + let test = TestSetup::new(); + test.declarative_part( + " +variable c0 : integer_vector(0 to 6); +", + ); + let code = test.snippet("c0(real)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("real"), + "real type 'REAL' cannot be used as a discrete range", + ErrorCode::MismatchedKinds, + )], + ) + } + + #[test] + fn cannot_slice_multi_dimensional_array() { + let test = TestSetup::new(); + test.declarative_part( + " +type arr_t is array (natural range 0 to 1, natural range 0 to 1) of character; +variable c0 : arr_t; +", + ); + let code = test.snippet("c0(0 to 1)"); + let mut diagnostics = Vec::new(); + let _ = test.name_resolve(&code, None, &mut diagnostics); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("c0(0 to 1)"), + "Cannot slice 2-dimensional array type 'arr_t'", + ErrorCode::MismatchedKinds, + )], + ) + } + + #[test] + fn scalar_type_attribute() { + let test = TestSetup::new(); + let code = test.snippet("integer'left"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))) + ); + let code = test.snippet("integer'right"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))) + ); + let code = test.snippet("integer'high"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))) + ); + let code = test.snippet("integer'low"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))) + ); + } + + #[test] + fn array_type_attribute() { + let test = TestSetup::new(); + test.declarative_part( + " +type arr_t is array (integer range 0 to 3) of integer; + ", + ); + let code = test.snippet("arr_t'left"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))) + ); + } + + #[test] + fn array_2d_type_attribute() { + let test = TestSetup::new(); + test.declarative_part( + " +type arr_t is array (integer range 0 to 3, character range 'a' to 'c') of integer; + ", + ); + let code = test.snippet("arr_t'left(1)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer") + ))) + ); + + let code = test.snippet("arr_t'left(2)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("character") + ))) + ); + + let code = test.snippet("arr_t'left(3)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("3"), + "Index 3 out of range for array with 2 dimensions, expected 1 to 2", + ErrorCode::DimensionMismatch, + )], + ); + + let code = test.snippet("arr_t'left(1+1)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("1+1"), + "Expected an integer literal", + ErrorCode::MismatchedKinds, + )], + ) + } + + #[test] + fn length_attribute_of_array_type() { + let test = TestSetup::new(); + + test.declarative_part( + " +type arr_t is array (integer range 0 to 3) of integer; + ", + ); + + let code = test.snippet("arr_t'length"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).universal_integer().into() + ))) + ); + } + + #[test] + fn length_attribute_of_array_object() { + let test = TestSetup::new(); + + test.declarative_part( + " +type arr_t is array (integer range 0 to 3) of integer; +constant c0 : arr_t := (others => 0); + ", + ); + + let code = test.snippet("c0'length"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).universal_integer().into() + ))) + ); + } + + #[test] + fn ascending_descending() { + let test = TestSetup::new(); + + test.declarative_part( + " +type arr_t is array (integer range 0 to 3) of integer; +constant c0 : arr_t := (others => 0); + ", + ); + + let code = test.snippet("c0'ascending"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).boolean() + ))) + ); + } + + #[test] + fn image() { + let test = TestSetup::new(); + + let code = test.snippet("natural'image(0)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).string() + ))) + ); + + let code = test.snippet("natural'image"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).string() + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.pos(), + "'image attribute requires a single argument", + ErrorCode::Unassociated, + )], + ) + } + + #[test] + fn attribute_no_arg() { + let test = TestSetup::new(); + + let code = test.snippet("integer'low(0)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).integer() + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("0"), + "'low attribute does not take an argument", + ErrorCode::TooManyArguments, + )], + ) + } + + #[test] + fn value() { + let test = TestSetup::new(); + + let code = test.snippet("integer'value(\"0\")"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).integer() + ))) + ); + } + + #[test] + fn discrete_attributes() { + let test = TestSetup::new(); + + let code = test.snippet("character'pos('a')"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).universal_integer().into() + ))) + ); + + let code = test.snippet("character'val(0)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).character() + ))) + ); + + let code = test.snippet("character'val('a')"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).character() + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("'a'"), + "character literal does not match type universal_integer", + ErrorCode::TypeMismatch, + )], + ); + + let code = test.snippet("character'succ('a')"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).character() + ))) + ); + + let code = test.snippet("character'pred('a')"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).character() + ))) + ); + + let code = test.snippet("character'leftof('a')"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).character() + ))) + ); + + let code = test.snippet("character'rightof('a')"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).character() + ))) + ); + } + + /// This is a regression test + #[test] + fn val_attribute_with_overloaded_name() { + let test = TestSetup::new(); + + test.declarative_part( + " +impure function pop return integer is +begin +end function; + +impure function pop return boolean is +begin +end function; + +type enum_t is (alpha, beta); + ", + ); + let code = test.snippet("enum_t'val(pop)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("enum_t") + ))) + ); + } + + #[test] + fn signal_attributes_on_non_signal() { + let test = TestSetup::new(); + test.declarative_part( + " +variable thevar : integer; + ", + ); + let code = test.snippet("thevar'delayed(0 ns)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("thevar"), + "Expected signal prefix for 'delayed attribute, got variable 'thevar'", + ErrorCode::MismatchedKinds, + )], + ) + } + + #[test] + fn signal_attributes() { + let test = TestSetup::new(); + test.declarative_part( + " +signal thesig : integer; + ", + ); + + let code = test.snippet("thesig'delayed(0 ns)"); + let integer = test.ctx(&code.tokenize()).integer(); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(integer) + ); + + let code = test.snippet("thesig'delayed"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(integer) + ); + + let code = test.snippet("thesig'stable(0 ns)"); + let boolean = test.ctx(&code.tokenize()).boolean(); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(boolean) + ); + + let code = test.snippet("thesig'quiet(0 ns)"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(boolean) + ); + + let code = test.snippet("thesig'transaction"); + let bit = test.ctx(&code.tokenize()).bit(); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(bit) + ); + + let code = test.snippet("thesig'event"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(boolean) + ); + + let code = test.snippet("thesig'active"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(boolean) + ); + + let code = test.snippet("thesig'last_event"); + let time = test.ctx(&code.tokenize()).time(); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(time) + ); + + let code = test.snippet("thesig'last_active"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(time) + ); + + let code = test.snippet("thesig'last_value"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(integer) + ); + + let code = test.snippet("thesig'driving"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(boolean) + ); + + let code = test.snippet("thesig'driving_value"); + assert_matches!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::ObjectName(ObjectName { + base: ObjectBase::Object(_), + type_mark: typ + })) if typ == Some(integer) + ); + } + + #[test] + fn missing_attribute() { + let test = TestSetup::new(); + test.declarative_part( + " +variable thevar : integer; + ", + ); + let code = test.snippet("thevar'missing"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "Unknown attribute 'missing", + ErrorCode::Unresolved, + )], + ) + } + + #[test] + fn range_attribute() { + let test = TestSetup::new(); + test.declarative_part( + " +variable thevar : integer_vector(0 to 1); + ", + ); + let code = test.snippet("thevar'range"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code, + "Range cannot be used as an expression", + ErrorCode::MismatchedKinds, + )], + ) + } + + #[test] + fn name_attributes() { + let test = TestSetup::new(); + test.declarative_part( + " +signal thesig : integer; + ", + ); + + let code = test.snippet("thesig'simple_name"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).string() + ))) + ); + let code = test.snippet("thesig'instance_name"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).string() + ))) + ); + let code = test.snippet("thesig'path_name"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).string() + ))) + ); + } + + #[test] + fn integer_type_conversion() { + let test = TestSetup::new(); + let code = test.snippet("integer(1.0)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).integer() + ))) + ); + + let code = test.snippet("real(1)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).real() + ))) + ); + } + + #[test] + fn integer_type_conversion_not_closely_related() { + let test = TestSetup::new(); + let code = test.snippet("integer('a')"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).integer() + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("'a'"), + "type 'CHARACTER' cannot be converted to integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ); + + let code = test.snippet("real(false)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.ctx(&code.tokenize()).real() + ))) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("false"), + "type 'BOOLEAN' cannot be converted to real type 'REAL'", + ErrorCode::TypeMismatch, + )], + ); + } + + #[test] + fn array_type_conversion() { + let test = TestSetup::new(); + test.declarative_part( + " +type character_vector is array (natural range 0 to 1) of character; + ", + ); + let code = test.snippet("character_vector(string'(\"01\"))"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("character_vector") + ))) + ); + } + + #[test] + fn array_type_conversion_not_closely_related() { + let test = TestSetup::new(); + test.declarative_part( + " +type character_vector_2d is array (natural range 0 to 1, natural range 0 to 2) of character; + ", + ); + + // Dimensionality mismatch + let code = test.snippet("character_vector_2d(string'(\"01\"))"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("character_vector_2d") + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("string'(\"01\")"), + "array type 'STRING' cannot be converted to array type 'character_vector_2d'", + ErrorCode::TypeMismatch, + )], + ); + + // Element type mismatch + let code = test.snippet("integer_vector(string'(\"01\"))"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("integer_vector") + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("string'(\"01\")"), + "array type 'STRING' cannot be converted to array type 'INTEGER_VECTOR'", + ErrorCode::TypeMismatch, + )], + ); + } + + #[test] + fn array_type_conversion_of_closely_related_elements() { + let test = TestSetup::new(); + test.declarative_part( + " +type character_vector1 is array (natural range 0 to 1) of character; +type character_vector2 is array (natural range 0 to 1) of character; +type character_matrix1 is array (natural range 0 to 1) of character_vector1; +type character_matrix2 is array (natural range 0 to 1) of character_vector2; + +constant c0 : character_matrix1 := (others => (others => 'a')); + ", + ); + let code = test.snippet("character_matrix2(c0)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("character_matrix2") + ))) + ); + } + + #[test] + fn identical_type_conversion() { + let test = TestSetup::new(); + test.declarative_part( + " +type enum_t is (alpha, beta); + ", + ); + let code = test.snippet("enum_t(alpha)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Unambiguous( + test.lookup_type("enum_t") + ))) + ); + } + + #[test] + fn ambiguous_function() { + let test = TestSetup::new(); + test.declarative_part( + " + function myfun(arg: integer) return integer; + function myfun(arg: integer) return real; + ", + ); + let code = test.snippet("myfun(0)"); + assert_eq!( + test.name_resolve(&code, None, &mut NoDiagnostics), + Ok(ResolvedName::Expression(DisambiguatedType::Ambiguous( + vec![ + test.ctx(&code.tokenize()).real().base(), + test.ctx(&code.tokenize()).integer().base() + ] + .into_iter() + .collect() + ))) + ); + } + + #[test] + fn ambiguous_function_with_ttyp() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " + function f1 return integer; + function f1 return character; + function myfun(arg: integer) return integer; + function myfun(arg: character) return integer; + ", + ); + let code = test.snippet("myfun(f1)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve( + &code, + Some(test.ctx(&code.tokenize()).integer()), + &mut diagnostics + ), + Ok(ResolvedName::Expression(DisambiguatedType::Ambiguous( + vec![test.ctx(&code.tokenize()).integer().base()] + .into_iter() + .collect() + ))) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("myfun"), + "Ambiguous call to 'myfun'", + ErrorCode::AmbiguousCall, + ) + .related( + decl.s("myfun", 1), + "Might be function myfun[INTEGER return INTEGER]", + ) + .related( + decl.s("myfun", 2), + "Might be function myfun[CHARACTER return INTEGER]", + )], + ) + } + + #[test] + fn ambiguous_function_with_suffix() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " + type rec1_t is record + elem1: natural; + end record; + + type rec2_t is record + elem1: natural; + end record; + + function myfun(arg: integer) return rec1_t; + function myfun(arg: integer) return rec2_t; + ", + ); + let code = test.snippet("myfun(0).elem1"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.name_resolve(&code, None, &mut diagnostics), + Err(EvalError::Unknown) + ); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("myfun"), + "Ambiguous call to 'myfun'", + ErrorCode::AmbiguousCall, + ) + .related( + decl.s("myfun", 1), + "Might be function myfun[INTEGER return rec1_t]", + ) + .related( + decl.s("myfun", 2), + "Might be function myfun[INTEGER return rec2_t]", + )], + ) + } + + #[test] + fn signal_attributes_can_be_used_in_sensitivity_lists() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + "\ +entity mwe is +end entity; + +architecture bhv of mwe is + signal foo: bit; +begin + process is + begin + wait on foo'transaction; + end process; +end architecture; + ", + ); + check_no_diagnostics(&builder.analyze()); + } +} diff --git a/vhdl_lang/src/analysis/overloaded.rs b/vhdl_lang/src/analysis/overloaded.rs new file mode 100644 index 0000000..2763a93 --- /dev/null +++ b/vhdl_lang/src/analysis/overloaded.rs @@ -0,0 +1,769 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use fnv::FnvHashSet; +use vhdl_lang::TokenAccess; + +use super::analyze::*; +use super::expression::ExpressionType; +use super::scope::*; +use crate::ast::search::clear_references; +use crate::ast::token_range::WithToken; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Disambiguated<'a> { + Unambiguous(OverloadedEnt<'a>), + Ambiguous(Vec>), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum DisambiguatedType<'a> { + Unambiguous(TypeEnt<'a>), + Ambiguous(FnvHashSet>), +} +#[derive(Copy, Clone)] +pub enum SubprogramKind<'a> { + Function(Option>), + Procedure, +} + +// The reason a subprogram was rejected as a candidate of a call +#[derive(Debug)] +enum Rejection<'a> { + // The return type of the subprogram is not correct + ReturnType, + + // The subprogram is a procedure but we expected a function + Procedure, + + // The amount of actuals or named actuals do not match formals + MissingFormals(Vec>), +} + +struct Candidate<'a> { + ent: OverloadedEnt<'a>, + rejection: Option>, +} + +struct Candidates<'a>(Vec>); + +impl<'a> Candidates<'a> { + fn new(candidates: &OverloadedName<'a>) -> Self { + Self( + candidates + .entities() + .map(|ent| Candidate { + ent, + rejection: None, + }) + .collect(), + ) + } + + fn remaining(&mut self) -> impl Iterator> { + self.0.iter_mut().filter(|cand| cand.rejection.is_none()) + } + + fn split(self) -> (Vec>, Vec>) { + let (remaining, rejected): (Vec<_>, Vec<_>) = self + .0 + .into_iter() + .partition(|cand| cand.rejection.is_none()); + let remaining: Vec<_> = remaining.into_iter().map(|cand| cand.ent).collect(); + (remaining, rejected) + } + + fn finish( + self, + ctx: &dyn TokenAccess, + name: &WithToken, + ttyp: Option>, + ) -> Result, Diagnostic> { + let (remaining, mut rejected) = self.split(); + + if remaining.len() == 1 { + Ok(Disambiguated::Unambiguous(remaining[0])) + } else if !remaining.is_empty() { + Ok(Disambiguated::Ambiguous(remaining)) + } else { + if let [ref single] = rejected.as_slice() { + if let Some(ttyp) = ttyp { + if matches!(single.rejection, Some(Rejection::ReturnType)) + && matches!(single.ent.kind(), Overloaded::EnumLiteral(_)) + { + // Special case to get better error for single rejected enumeration literal + // For example when assigning true to an integer. + return Err(Diagnostic::new( + name.pos(ctx), + format!("'{}' does not match {}", name, ttyp.describe()), + ErrorCode::TypeMismatch, + )); + } + } + } + + let (err_prefix, code) = if rejected.len() == 1 { + // Provide better error for unique function name + ("Invalid call to", ErrorCode::InvalidCall) + } else { + ("Could not resolve", ErrorCode::Unresolved) + }; + + let mut diag = Diagnostic::new(name.pos(ctx), format!("{err_prefix} '{name}'"), code); + + rejected.sort_by(|x, y| x.ent.decl_pos().cmp(&y.ent.decl_pos())); + + for cand in rejected { + if let Some(decl_pos) = cand.ent.decl_pos() { + let rejection = cand.rejection.unwrap(); + + match rejection { + Rejection::ReturnType => diag.add_related( + decl_pos, + format!("Does not match return type of {}", cand.ent.describe()), + ), + Rejection::Procedure => diag.add_related( + decl_pos, + format!( + "Procedure {} cannot be used as a function", + cand.ent.describe() + ), + ), + Rejection::MissingFormals(missing) => { + for formal in missing.iter() { + diag.add_related( + decl_pos, + format!("Missing association of {}", formal.describe()), + ) + } + } + }; + } + } + Err(diag) + } + } +} + +impl<'a> Disambiguated<'a> { + pub fn into_type(self) -> DisambiguatedType<'a> { + match self { + Disambiguated::Unambiguous(ent) => { + DisambiguatedType::Unambiguous(ent.return_type().unwrap()) + } + Disambiguated::Ambiguous(overloaded) => DisambiguatedType::Ambiguous( + overloaded + .into_iter() + .map(|e| e.return_type().unwrap().base()) + .collect(), + ), + } + } +} + +#[derive(Clone)] +pub(super) struct ResolvedCall<'a> { + pub subpgm: OverloadedEnt<'a>, + pub formals: Vec>, +} + +impl<'a> AsRef> for OverloadedEnt<'a> { + fn as_ref(&self) -> &OverloadedEnt<'a> { + self + } +} + +impl<'a> AsRef> for ResolvedCall<'a> { + fn as_ref(&self) -> &OverloadedEnt<'a> { + &self.subpgm + } +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + /// Typecheck one overloaded call where the exact subprogram is known + pub fn check_call( + &self, + scope: &Scope<'a>, + error_pos: &SrcPos, + ent: OverloadedEnt<'a>, + assocs: &mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + self.check_association(error_pos, ent.formals(), scope, assocs, diagnostics)?; + Ok(()) + } + + fn disambiguate_by_kind(candidates: &mut Vec>, kind: SubprogramKind<'a>) { + match kind { + SubprogramKind::Function(_) => candidates.retain(|cand| cand.is_function()), + SubprogramKind::Procedure => candidates.retain(|cand| cand.is_procedure()), + }; + } + + fn disambiguate_by_assoc_formals( + &self, + scope: &Scope<'a>, + call_pos: &SrcPos, + candidates: &[OverloadedEnt<'a>], + assocs: &mut [AssociationElement], + ) -> EvalResult>> { + let mut result = Vec::with_capacity(candidates.len()); + for ent in candidates.iter() { + if let Some(resolved) = as_fatal(self.resolve_association_formals( + call_pos, + ent.formals(), + scope, + assocs, + &mut NullDiagnostics, + ))? { + result.push(ResolvedCall { + subpgm: *ent, + formals: resolved, + }); + } + + for elem in assocs.iter_mut() { + clear_references(elem, self.ctx); + } + } + + Ok(result) + } + + fn actual_types( + &self, + scope: &Scope<'a>, + assocs: &mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>>> { + let mut actual_types = Vec::with_capacity(assocs.len()); + + for assoc in assocs.iter_mut() { + match &mut assoc.actual.item { + ActualPart::Expression(expr) => { + let actual_type = + self.expr_pos_type(scope, assoc.actual.span, expr, diagnostics)?; + actual_types.push(Some(actual_type)); + } + ActualPart::Open => { + actual_types.push(None); + } + } + } + + Ok(actual_types) + } + + #[allow(clippy::too_many_arguments)] + pub fn disambiguate( + &self, + scope: &Scope<'a>, + call_pos: &SrcPos, // The position of the entire call + call_name: &WithToken, // The position and name of the subprogram name in the call + assocs: &mut [AssociationElement], + kind: SubprogramKind<'a>, + all_overloaded: Vec>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + if all_overloaded.len() == 1 { + let ent = all_overloaded[0]; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } + + let mut ok_kind = all_overloaded.clone(); + Self::disambiguate_by_kind(&mut ok_kind, kind); + + // Does not need disambiguation + if ok_kind.len() == 1 { + let ent = ok_kind[0]; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if ok_kind.is_empty() { + diagnostics.push(Diagnostic::ambiguous(self.ctx, call_name, all_overloaded)); + return Err(EvalError::Unknown); + } + + // Disambiguate based on uninstantiated subprogram + ok_kind.retain(|ent| !ent.is_uninst_subprogram()); + + if ok_kind.len() == 1 { + let ent = ok_kind[0]; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if ok_kind.is_empty() { + diagnostics.add( + call_name.pos(self.ctx), + format!("uninstantiated subprogram {} cannot be called", call_name), + ErrorCode::InvalidCall, + ); + return Err(EvalError::Unknown); + } + + let ok_formals = self.disambiguate_by_assoc_formals(scope, call_pos, &ok_kind, assocs)?; + + // Only one candidate matched actual/formal profile + if ok_formals.len() == 1 { + let ent = ok_formals[0].subpgm; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if ok_formals.is_empty() { + // No candidate matched actual/formal profile + diagnostics.push(Diagnostic::ambiguous(self.ctx, call_name, ok_kind)); + return Err(EvalError::Unknown); + } + + let actual_types = self.actual_types(scope, assocs, diagnostics)?; + + let mut ok_assoc_types = ok_formals.clone(); + self.implicit_matcher() + .disambiguate_by_assoc_types(&actual_types, &mut ok_assoc_types); + + if ok_assoc_types.len() == 1 { + let ent = ok_assoc_types[0].subpgm; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if ok_assoc_types.is_empty() { + diagnostics.push(Diagnostic::ambiguous( + self.ctx, + call_name, + ok_formals.into_iter().map(|resolved| resolved.subpgm), + )); + return Err(EvalError::Unknown); + } + + let ok_return_type = if let SubprogramKind::Function(rtyp) = kind { + let mut ok_return_type = ok_assoc_types.clone(); + self.any_matcher() + .disambiguate_op_by_return_type(&mut ok_return_type, rtyp); + + // Only one candidate matches type profile, check it + if ok_return_type.len() == 1 { + let ent = ok_return_type[0].subpgm; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if ok_return_type.is_empty() { + diagnostics.push(Diagnostic::ambiguous( + self.ctx, + call_name, + ok_assoc_types.into_iter().map(|resolved| resolved.subpgm), + )); + return Err(EvalError::Unknown); + } + ok_return_type + } else { + ok_assoc_types + }; + + let mut strict_ok_assoc_types = ok_return_type.clone(); + self.strict_matcher() + .disambiguate_by_assoc_types(&actual_types, &mut strict_ok_assoc_types); + + if strict_ok_assoc_types.len() == 1 { + let ent = strict_ok_assoc_types[0].subpgm; + self.check_call(scope, call_pos, ent, assocs, diagnostics)?; + return Ok(Disambiguated::Unambiguous(ent)); + } else if strict_ok_assoc_types.is_empty() { + // Do not disambiguate away to emtpy result + strict_ok_assoc_types.clone_from(&ok_return_type); + } + + Ok(Disambiguated::Ambiguous( + strict_ok_assoc_types + .into_iter() + .map(|resolved| resolved.subpgm) + .collect(), + )) + } + + pub fn disambiguate_no_actuals( + &self, + name: &WithToken, + ttyp: Option>, + overloaded: &OverloadedName<'a>, + ) -> Result>, Diagnostic> { + let mut candidates = Candidates::new(overloaded); + + let tbase = ttyp.map(|ttyp| ttyp.base()); + + for cand in candidates.remaining() { + if !cand.ent.signature().can_be_called_without_actuals() { + cand.rejection = Some(Rejection::MissingFormals( + cand.ent.signature().formals_without_defaults().collect(), + )); + } else if let Some(return_type) = cand.ent.return_type() { + if let Some(tbase) = tbase { + if !self.can_be_target_type(return_type, tbase) { + cand.rejection = Some(Rejection::ReturnType); + } + } + } else { + cand.rejection = Some(Rejection::Procedure); + } + } + + Ok(Some(candidates.finish(self.ctx, name, ttyp)?)) + } +} + +impl Diagnostic { + fn ambiguous<'a>( + ctx: &dyn TokenAccess, + name: &WithToken, + rejected: impl IntoIterator>, + ) -> Self { + let mut diag = Diagnostic::new( + name.pos(ctx), + format!("Could not resolve call to '{}'", name.item.designator()), + ErrorCode::AmbiguousCall, + ); + diag.add_subprogram_candidates("Does not match", rejected); + diag + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::analysis::overloaded; + use crate::analysis::tests::TestSetup; + use crate::data::DiagnosticHandler; + use crate::syntax::test::check_diagnostics; + use crate::syntax::test::Code; + + impl<'a> TestSetup<'a> { + fn disambiguate( + &'a self, + code: &Code, + ttyp: Option>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> Option> { + let mut fcall = code.function_call(); + + let des = if let Name::Designator(des) = &fcall.item.name.item { + WithToken::new(des.item.clone(), fcall.span.start_token) + } else { + panic!("Expected designator") + }; + + let overloaded = if let NamedEntities::Overloaded(overloaded) = + self.scope.lookup(&des.item).unwrap() + { + overloaded + } else { + panic!("Expected overloaded") + }; + + as_fatal(self.ctx(&code.tokenize()).disambiguate( + &self.scope, + &fcall.pos(&code.tokenize()), + &des, + &mut fcall.item.parameters.items, + overloaded::SubprogramKind::Function(ttyp), + overloaded.entities().collect(), + diagnostics, + )) + .unwrap() + } + } + + #[test] + fn single_fcall() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun return integer; + ", + ); + + let func = test.lookup_overloaded(decl.s1("myfun")); + + assert_eq!( + test.disambiguate(&test.snippet("myfun"), None, &mut NoDiagnostics), + Some(Disambiguated::Unambiguous(func)) + ); + } + + #[test] + fn single_fcall_bad_type() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg : integer) return integer; + ", + ); + + let mut diagnostics = Vec::new(); + let call = test.snippet("myfun('c')"); + assert_eq!( + test.disambiguate(&call, None, &mut diagnostics), + Some(Disambiguated::Unambiguous( + test.lookup_overloaded(decl.s1("myfun")) + )), + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + call.s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ); + } + + #[test] + fn disambiguate_fcall_based_on_named_association() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : integer) return integer; +function myfun(arg2 : integer) return character; + ", + ); + + let func = test.lookup_overloaded(decl.s1("myfun(arg1").s1("myfun")); + + assert_eq!( + test.disambiguate(&test.snippet("myfun(arg1 => 0)"), None, &mut NoDiagnostics), + Some(Disambiguated::Unambiguous(func)) + ); + } + + #[test] + fn disambiguate_fcall_formal_mismatch() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : integer) return integer; + ", + ); + + let fcall = test.snippet("myfun(missing => 0)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.disambiguate(&fcall, None, &mut diagnostics), + Some(Disambiguated::Unambiguous( + test.lookup_overloaded(decl.s1("myfun")) + )) + ); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + fcall.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + fcall, + "No association of parameter 'arg1'", + ErrorCode::Unassociated, + ) + .related(decl.s1("arg1"), "Defined here"), + ], + ); + } + + #[test] + fn disambiguate_fcall_no_candidates_matching_actuals() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : integer) return integer; +function myfun(arg2 : integer) return character; + ", + ); + + let fcall = test.snippet("myfun"); + let mut diagnostics = Vec::new(); + assert_eq!(test.disambiguate(&fcall, None, &mut diagnostics), None); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + fcall.s1("myfun"), + "Could not resolve call to 'myfun'", + ErrorCode::AmbiguousCall, + ) + .related( + decl.s1("myfun"), + "Does not match function myfun[INTEGER return INTEGER]", + ) + .related( + decl.s("myfun", 2), + "Does not match function myfun[INTEGER return CHARACTER]", + )], + ); + } + + #[test] + fn disambiguate_fcall_based_on_type() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : character) return integer; +function myfun(arg1 : integer) return integer; + ", + ); + let fcall = test.snippet("myfun('c')"); + assert_eq!( + test.disambiguate(&fcall, None, &mut NoDiagnostics), + Some(Disambiguated::Unambiguous( + test.lookup_overloaded(decl.s1("myfun")) + )) + ); + + let fcall = test.snippet("myfun(0)"); + assert_eq!( + test.disambiguate(&fcall, None, &mut NoDiagnostics), + Some(Disambiguated::Unambiguous( + test.lookup_overloaded(decl.s("myfun", 2)) + )) + ); + } + + #[test] + fn ambiguous_call() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : integer) return character; +function myfun(arg1 : integer) return integer; + ", + ); + let fcall = test.snippet("myfun(0)"); + assert_eq!( + test.disambiguate(&fcall, None, &mut NoDiagnostics), + Some(Disambiguated::Ambiguous(vec![ + test.lookup_overloaded(decl.s("myfun", 2)), + test.lookup_overloaded(decl.s1("myfun")), + ])) + ); + } + + #[test] + fn ambiguous_call_without_match() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : character) return integer; +function myfun(arg1 : character) return character; + ", + ); + let fcall = test.snippet("myfun(0)"); + let mut diagnostics = Vec::new(); + assert_eq!(test.disambiguate(&fcall, None, &mut diagnostics), None); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + fcall.s1("myfun"), + "Could not resolve call to 'myfun'", + ErrorCode::AmbiguousCall, + ) + .related( + decl.s1("myfun"), + "Does not match function myfun[CHARACTER return INTEGER]", + ) + .related( + decl.s("myfun", 2), + "Does not match function myfun[CHARACTER return CHARACTER]", + )], + ); + } + + #[test] + fn disambiguates_target_type() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : integer) return integer; +function myfun(arg1 : integer) return character; + ", + ); + let fcall = test.snippet("myfun(0)"); + assert_eq!( + test.disambiguate( + &fcall, + Some(test.lookup_type("integer")), + &mut NoDiagnostics + ), + Some(Disambiguated::Unambiguous( + test.lookup_overloaded(decl.s1("myfun")) + )) + ); + } + + #[test] + fn target_type_no_match() { + let test = TestSetup::new(); + let decl = test.declarative_part( + " +function myfun(arg1 : integer) return integer; +function myfun(arg1 : integer) return character; + ", + ); + let fcall = test.snippet("myfun(0)"); + let mut diagnostics = Vec::new(); + + assert_eq!( + test.disambiguate(&fcall, Some(test.lookup_type("boolean")), &mut diagnostics), + None, + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + fcall.s1("myfun"), + "Could not resolve call to 'myfun'", + ErrorCode::AmbiguousCall, + ) + .related( + decl.s1("myfun"), + "Does not match function myfun[INTEGER return INTEGER]", + ) + .related( + decl.s("myfun", 2), + "Does not match function myfun[INTEGER return CHARACTER]", + )], + ) + } + + #[test] + fn ambiguous_builtin_favors_non_implicit_conversion_unary() { + let test = TestSetup::new(); + let code = test.snippet("to_string(0)"); + + let uint_to_string = test + .ctx(&code.tokenize()) + .universal_integer() + .lookup_implicit_of("TO_STRING"); + + assert_eq!( + test.disambiguate(&code, None, &mut NoDiagnostics), + Some(Disambiguated::Unambiguous(uint_to_string)) + ); + } + + #[test] + fn ambiguous_builtin_favors_non_implicit_conversion_binary() { + let test = TestSetup::new(); + let code = test.snippet("minimum(0, integer'(0))"); + + let minimum = test + .ctx(&code.tokenize()) + .integer() + .lookup_implicit_of("MINIMUM"); + + assert_eq!( + test.disambiguate(&code, None, &mut NoDiagnostics), + Some(Disambiguated::Unambiguous(minimum)) + ); + } +} diff --git a/vhdl_lang/src/analysis/package_instance.rs b/vhdl_lang/src/analysis/package_instance.rs new file mode 100644 index 0000000..95b3fde --- /dev/null +++ b/vhdl_lang/src/analysis/package_instance.rs @@ -0,0 +1,627 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use fnv::FnvHashMap; +use vhdl_lang::SrcPos; + +use super::analyze::*; +use super::names::ResolvedName; +use super::scope::*; +use crate::ast::AssociationElement; +use crate::ast::Expression; +use crate::ast::Literal; +use crate::ast::Name; +use crate::ast::Operator; +use crate::ast::PackageInstantiation; +use crate::ast::{ActualPart, MapAspect}; +use crate::data::error_codes::ErrorCode; +use crate::data::DiagnosticHandler; +use crate::named_entity::*; +use crate::Diagnostic; +use crate::NullDiagnostics; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn generic_package_instance( + &self, + scope: &Scope<'a>, + package_ent: EntRef<'a>, + unit: &mut PackageInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let PackageInstantiation { + package_name, + generic_map, + .. + } = unit; + + let package_region = + self.analyze_package_instance_name(scope, package_name, diagnostics)?; + self.generic_instance( + package_ent, + scope, + unit.ident.tree.pos(self.ctx), + &package_region, + generic_map, + diagnostics, + ) + .map(|(region, _)| region) + } + + pub fn generic_map( + &self, + scope: &Scope<'a>, + generics: GpkgRegion<'a>, + generic_map: &mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult>> { + let mut mapping = FnvHashMap::default(); + + // @TODO check missing associations + for (idx, assoc) in generic_map.iter_mut().enumerate() { + let formal = if let Some(formal) = &mut assoc.formal { + let formal_pos = formal.pos(self.ctx); + if let Name::Designator(des) = &mut formal.item { + match generics.lookup(&formal_pos, &des.item) { + Ok((_, ent)) => { + des.set_unique_reference(&ent); + ent + } + Err(err) => { + diagnostics.push(err); + continue; + } + } + } else { + diagnostics.add( + formal.pos(self.ctx), + "Expected simple name for package generic formal", + ErrorCode::MismatchedKinds, + ); + continue; + } + } else if let Some(ent) = generics.nth(idx) { + ent + } else { + diagnostics.add( + assoc.actual.pos(self.ctx), + "Extra actual for generic map", + ErrorCode::TooManyArguments, + ); + continue; + }; + + match &mut assoc.actual.item { + ActualPart::Expression(expr) => match formal { + GpkgInterfaceEnt::Type(uninst_typ) => { + let typ = if let Expression::Name(name) = expr { + match name.as_mut() { + // Could be an array constraint such as integer_vector(0 to 3) + // @TODO we ignore the suffix for now + Name::Slice(prefix, drange) => { + let typ = self.type_name( + scope, + prefix.span, + &mut prefix.item, + diagnostics, + )?; + if let Type::Array { indexes, .. } = typ.base().kind() { + if let Some(Some(idx_typ)) = indexes.first() { + self.drange_with_ttyp( + scope, + (*idx_typ).into(), + drange, + diagnostics, + )?; + } + } else { + diagnostics.add( + assoc.actual.pos(self.ctx), + format!( + "Array constraint cannot be used for {}", + typ.describe() + ), + ErrorCode::TypeMismatch, + ); + } + typ + } + // Could be a record constraint such as rec_t(field(0 to 3)) + // @TODO we ignore the suffix for now + Name::CallOrIndexed(call) if call.could_be_indexed_name() => self + .type_name( + scope, + call.name.span, + &mut call.name.item, + diagnostics, + )?, + _ => self.type_name(scope, assoc.actual.span, name, diagnostics)?, + } + } else { + diagnostics.add( + assoc.actual.pos(self.ctx), + "Cannot map expression to type generic", + ErrorCode::MismatchedKinds, + ); + continue; + }; + + mapping.insert(uninst_typ.id(), typ); + } + GpkgInterfaceEnt::Constant(obj) => self.expr_pos_with_ttyp( + scope, + self.map_type_ent(&mapping, obj.type_mark(), scope), + assoc.actual.span, + expr, + diagnostics, + )?, + GpkgInterfaceEnt::Subprogram(target) => match expr { + Expression::Name(name) => { + let resolved = + self.name_resolve(scope, assoc.actual.span, name, diagnostics)?; + if let ResolvedName::Overloaded(des, overloaded) = resolved { + let signature = target.subprogram_key().map(|base_type| { + mapping + .get(&base_type.id()) + .map(|ent| ent.base()) + .unwrap_or(base_type) + }); + if let Some(ent) = overloaded.get(&signature) { + name.set_unique_reference(&ent); + } else { + let mut diag = Diagnostic::mismatched_kinds( + assoc.actual.pos(self.ctx), + format!( + "Cannot map '{des}' to subprogram generic {}{}", + target.designator(), + signature.key().describe() + ), + ); + + diag.add_subprogram_candidates( + "Does not match", + overloaded.entities(), + ); + + diagnostics.push(diag) + } + } else { + diagnostics.add( + assoc.actual.pos(self.ctx), + format!( + "Cannot map {} to subprogram generic", + resolved.describe() + ), + ErrorCode::MismatchedKinds, + ) + } + } + Expression::Literal(Literal::String(string)) => { + if Operator::from_latin1(string.clone()).is_none() { + diagnostics.add( + assoc.actual.pos(self.ctx), + "Invalid operator symbol", + ErrorCode::InvalidOperatorSymbol, + ); + } + } + _ => diagnostics.add( + assoc.actual.pos(self.ctx), + "Cannot map expression to subprogram generic", + ErrorCode::MismatchedKinds, + ), + }, + GpkgInterfaceEnt::Package(_) => match expr { + Expression::Name(name) => { + self.name_resolve(scope, assoc.actual.span, name, diagnostics)?; + } + _ => diagnostics.add( + assoc.actual.pos(self.ctx), + "Cannot map expression to package generic", + ErrorCode::MismatchedKinds, + ), + }, + }, + ActualPart::Open => { + // @TODO + } + } + } + Ok(mapping) + } + + pub fn generic_instance( + &self, + ent: EntRef<'a>, + scope: &Scope<'a>, + decl_pos: &SrcPos, + uninst_region: &Region<'a>, + generic_map: &mut Option, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult<(Region<'a>, FnvHashMap>)> { + let nested = scope.nested().in_package_declaration(); + let (generics, other) = uninst_region.to_package_generic(); + + let mapping = if let Some(generic_map) = generic_map { + self.generic_map( + &nested, + generics, + generic_map.list.items.as_mut_slice(), + diagnostics, + )? + } else { + FnvHashMap::default() + }; + + for uninst in other { + match self.instantiate(Some(ent), &mapping, uninst, &nested) { + Ok(inst) => { + // We ignore diagnostics here, for example when adding implicit operators EQ and NE for interface types + // They can collide if there are more than one interface type that map to the same actual type + nested.add(inst, &mut NullDiagnostics); + } + Err((err, code)) => { + let mut diag = Diagnostic::new(decl_pos, err, code); + if let Some(pos) = uninst.decl_pos() { + diag.add_related(pos, "When instantiating this declaration"); + } + diagnostics.push(diag); + } + } + } + + Ok((nested.into_region(), mapping)) + } + + pub(crate) fn instantiate( + &self, + parent: Option>, + mapping: &FnvHashMap>, + uninst: EntRef<'a>, + scope: &Scope<'a>, + ) -> Result, (String, ErrorCode)> { + let designator = uninst.designator().clone(); + + let decl_pos = uninst.decl_pos().cloned(); + + let inst = self.arena.alloc( + designator, + parent.or(uninst.parent), + Related::InstanceOf(uninst), + AnyEntKind::Library, // Will be immediately overwritten below + decl_pos, + uninst.src_span, + Some(self.source()), + ); + let kind = self.map_kind(Some(inst), mapping, uninst.kind(), scope)?; + unsafe { + inst.set_kind(kind); + } + + for implicit_uninst in uninst.implicits.iter() { + unsafe { + self.arena.add_implicit( + inst.id(), + self.instantiate(Some(inst), mapping, implicit_uninst, scope)?, + ); + } + } + + Ok(inst) + } + + fn map_kind( + &self, + parent: Option>, + mapping: &FnvHashMap>, + kind: &'a AnyEntKind<'a>, + scope: &Scope<'a>, + ) -> Result, (String, ErrorCode)> { + Ok(match kind { + AnyEntKind::ExternalAlias { class, type_mark } => AnyEntKind::ExternalAlias { + class: *class, + type_mark: self.map_type_ent(mapping, *type_mark, scope), + }, + AnyEntKind::ObjectAlias { + base_object, + type_mark, + } => AnyEntKind::ObjectAlias { + base_object: if let Some(obj) = + ObjectEnt::from_any(self.instantiate(None, mapping, base_object, scope)?) + { + obj + } else { + return Err(( + "Internal error, expected instantiated object to be object".to_owned(), + ErrorCode::Internal, + )); + }, + type_mark: self.map_type_ent(mapping, *type_mark, scope), + }, + AnyEntKind::File(subtype) => { + AnyEntKind::File(self.map_subtype(mapping, *subtype, scope)) + } + AnyEntKind::InterfaceFile(typ) => { + AnyEntKind::InterfaceFile(self.map_type_ent(mapping, *typ, scope)) + } + AnyEntKind::Component(region) => { + AnyEntKind::Component(self.map_region(parent, mapping, region, scope)?) + } + AnyEntKind::Attribute(typ) => { + AnyEntKind::Attribute(self.map_type_ent(mapping, *typ, scope)) + } + AnyEntKind::Overloaded(overloaded) => { + AnyEntKind::Overloaded(self.map_overloaded(parent, mapping, overloaded, scope)?) + } + AnyEntKind::Type(typ) => AnyEntKind::Type(self.map_type(parent, mapping, typ, scope)?), + AnyEntKind::ElementDeclaration(subtype) => { + AnyEntKind::ElementDeclaration(self.map_subtype(mapping, *subtype, scope)) + } + AnyEntKind::Sequential(s) => AnyEntKind::Sequential(*s), + AnyEntKind::Concurrent(c) => AnyEntKind::Concurrent(*c), + AnyEntKind::Object(obj) => AnyEntKind::Object(self.map_object(mapping, obj, scope)), + AnyEntKind::LoopParameter(typ) => AnyEntKind::LoopParameter( + typ.map(|typ| self.map_type_ent(mapping, typ.into(), scope).base()), + ), + AnyEntKind::PhysicalLiteral(typ) => { + AnyEntKind::PhysicalLiteral(self.map_type_ent(mapping, *typ, scope)) + } + AnyEntKind::DeferredConstant(subtype) => { + AnyEntKind::DeferredConstant(self.map_subtype(mapping, *subtype, scope)) + } + AnyEntKind::Library => AnyEntKind::Library, + AnyEntKind::Design(design) => match design { + Design::PackageInstance(region) => AnyEntKind::Design(Design::PackageInstance( + self.map_region(parent, mapping, region, scope)?, + )), + Design::InterfacePackageInstance(region) => { + AnyEntKind::Design(Design::InterfacePackageInstance( + self.map_region(parent, mapping, region, scope)?, + )) + } + _ => { + return Err(( + format!( + "Internal error, did not expect to instantiate {}", + design.describe() + ), + ErrorCode::Internal, + )); + } + }, + AnyEntKind::View(typ) => AnyEntKind::View(self.map_subtype(mapping, *typ, scope)), + }) + } + + fn map_overloaded( + &self, + parent: Option>, + mapping: &FnvHashMap>, + overloaded: &'a Overloaded<'a>, + scope: &Scope<'a>, + ) -> Result, (String, ErrorCode)> { + Ok(match overloaded { + Overloaded::SubprogramDecl(signature) => { + Overloaded::SubprogramDecl(self.map_signature(parent, mapping, signature, scope)?) + } + Overloaded::Subprogram(signature) => { + Overloaded::Subprogram(self.map_signature(parent, mapping, signature, scope)?) + } + Overloaded::UninstSubprogramDecl(signature, generic_map) => { + Overloaded::UninstSubprogramDecl( + self.map_signature(parent, mapping, signature, scope)?, + generic_map.clone(), + ) + } + Overloaded::UninstSubprogram(signature, generic_map) => Overloaded::UninstSubprogram( + self.map_signature(parent, mapping, signature, scope)?, + generic_map.clone(), + ), + Overloaded::InterfaceSubprogram(signature) => Overloaded::InterfaceSubprogram( + self.map_signature(parent, mapping, signature, scope)?, + ), + Overloaded::EnumLiteral(signature) => { + Overloaded::EnumLiteral(self.map_signature(parent, mapping, signature, scope)?) + } + Overloaded::Alias(alias) => { + let alias_inst = self.instantiate(parent, mapping, alias, scope)?; + + if let Some(overloaded) = OverloadedEnt::from_any(alias_inst) { + Overloaded::Alias(overloaded) + } else { + return Err(( + "Internal error, expected overloaded when instantiating overloaded entity" + .to_owned(), + ErrorCode::Internal, + )); + } + } + }) + } + + pub(crate) fn map_signature( + &self, + parent: Option>, + mapping: &FnvHashMap>, + signature: &'a Signature<'a>, + scope: &Scope<'a>, + ) -> Result, (String, ErrorCode)> { + let Signature { + formals, + return_type, + } = signature; + + let FormalRegion { + typ, + entities: uninst_entities, + } = formals; + + let mut inst_entities = Vec::with_capacity(uninst_entities.len()); + for uninst in uninst_entities { + let inst = self.instantiate(parent, mapping, uninst, scope)?; + + if let Some(inst) = InterfaceEnt::from_any(inst) { + inst_entities.push(inst); + } else { + return Err(( + "Internal error, expected interface to be instantiated as interface".to_owned(), + ErrorCode::Internal, + )); + } + } + + Ok(Signature { + formals: FormalRegion { + typ: *typ, + entities: inst_entities, + }, + return_type: return_type.map(|typ| self.map_type_ent(mapping, typ, scope)), + }) + } + + fn map_region( + &self, + parent: Option>, + mapping: &FnvHashMap>, + region: &'a Region<'a>, + scope: &Scope<'a>, + ) -> Result, (String, ErrorCode)> { + let Region { + entities: uninst_entities, + kind, + .. + } = region; + + let mut inst_region = Region { + kind: *kind, + ..Region::default() + }; + + for uninst in uninst_entities.values() { + match uninst { + NamedEntities::Single(uninst) => { + let inst = self.instantiate(parent, mapping, uninst, scope)?; + inst_region.add(inst, &mut NullDiagnostics); + } + NamedEntities::Overloaded(overloaded) => { + for uninst in overloaded.entities() { + let inst = self.instantiate(parent, mapping, uninst.into(), scope)?; + inst_region.add(inst, &mut NullDiagnostics); + } + } + } + } + + Ok(inst_region) + } + + fn map_type( + &self, + parent: Option>, + mapping: &FnvHashMap>, + typ: &'a Type<'a>, + scope: &Scope<'a>, + ) -> Result, (String, ErrorCode)> { + Ok(match typ { + Type::Array { indexes, elem_type } => { + let mut mapped_indexes = Vec::with_capacity(indexes.len()); + for index_typ in indexes.iter() { + mapped_indexes.push(index_typ.map(|index_typ| { + self.map_type_ent(mapping, index_typ.into(), scope).base() + })) + } + + Type::Array { + indexes: mapped_indexes, + elem_type: self.map_type_ent(mapping, *elem_type, scope), + } + } + Type::Enum(symbols) => Type::Enum(symbols.clone()), + Type::Integer => Type::Integer, + Type::Real => Type::Real, + Type::Physical => Type::Physical, + Type::Access(subtype) => Type::Access(self.map_subtype(mapping, *subtype, scope)), + Type::Record(record_region) => { + let mut elems = Vec::with_capacity(record_region.elems.len()); + for uninst in record_region.elems.iter() { + let inst = self.instantiate(parent, mapping, uninst, scope)?; + + if let Some(inst) = RecordElement::from_any(inst) { + elems.push(inst); + } else { + return Err(("Internal error, expected instantiated record element to be record element".to_owned(), ErrorCode::Internal)); + } + } + Type::Record(RecordRegion { elems }) + } + Type::Subtype(subtype) => Type::Subtype(self.map_subtype(mapping, *subtype, scope)), + Type::Protected(region, is_body) => { + Type::Protected(self.map_region(parent, mapping, region, scope)?, *is_body) + } + Type::File => Type::File, + Type::Alias(typ) => Type::Alias(self.map_type_ent(mapping, *typ, scope)), + Type::Universal(utyp) => Type::Universal(*utyp), + Type::Incomplete => Type::Incomplete, + Type::Interface => Type::Interface, + }) + } + + fn map_object( + &self, + mapping: &FnvHashMap>, + obj: &Object<'a>, + scope: &Scope<'a>, + ) -> Object<'a> { + let Object { + class, + iface, + subtype, + has_default, + } = obj; + + Object { + class: *class, + iface: iface.clone(), + subtype: self.map_subtype(mapping, *subtype, scope), + has_default: *has_default, + } + } + + fn map_type_ent( + &self, + mapping: &FnvHashMap>, + typ: TypeEnt<'a>, + scope: &Scope<'a>, + ) -> TypeEnt<'a> { + match mapping.get(&typ.id()) { + None => { + if let Some(entity) = scope + .lookup(&typ.designator) + .ok() + .and_then(|result| result.into_non_overloaded().ok()) + .and_then(TypeEnt::from_any) + { + entity + } else { + typ + } + } + Some(typ) => *typ, + } + } + + fn map_subtype( + &self, + mapping: &FnvHashMap>, + subtype: Subtype<'a>, + scope: &Scope<'a>, + ) -> Subtype<'a> { + let Subtype { type_mark } = subtype; + + Subtype { + type_mark: self.map_type_ent(mapping, type_mark, scope), + } + } +} diff --git a/vhdl_lang/src/analysis/range.rs b/vhdl_lang/src/analysis/range.rs new file mode 100644 index 0000000..194a779 --- /dev/null +++ b/vhdl_lang/src/analysis/range.rs @@ -0,0 +1,663 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use super::analyze::*; +use super::expression::ExpressionType; +use super::names::AttributeSuffix; +use super::names::ResolvedName; +use super::overloaded::Disambiguated; +use super::overloaded::DisambiguatedType; +use super::scope::*; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::Range; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn range_unknown_typ( + &self, + scope: &Scope<'a>, + range: &mut Range, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + as_fatal(self.range_type(scope, range, diagnostics))?; + Ok(()) + } + + pub fn drange_unknown_type( + &self, + scope: &Scope<'a>, + drange: &mut DiscreteRange, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + as_fatal(self.drange_type(scope, drange, diagnostics))?; + Ok(()) + } + + fn range_expr_type( + &self, + scope: &Scope<'a>, + expr: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match self.expr_type(scope, expr, diagnostics)? { + ExpressionType::Unambiguous(typ) => { + if typ.base().is_scalar() { + Ok(DisambiguatedType::Unambiguous(typ)) + } else { + diagnostics.add( + expr.pos(self.ctx), + format!("Non-scalar {} cannot be used in a range", typ.describe()), + ErrorCode::NonScalarInRange, + ); + Err(EvalError::Unknown) + } + } + ExpressionType::Ambiguous(types) => Ok(DisambiguatedType::Ambiguous( + types.into_iter().filter(|typ| typ.is_scalar()).collect(), + )), + ExpressionType::String | ExpressionType::Null | ExpressionType::Aggregate => { + diagnostics.add( + expr.pos(self.ctx), + "Non-scalar expression cannot be used in a range", + ErrorCode::NonScalarInRange, + ); + Err(EvalError::Unknown) + } + } + } + + pub fn range_attribute_type( + &self, + scope: &Scope<'a>, + attr: &mut AttributeName, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let resolved = + self.name_resolve(scope, attr.name.span, &mut attr.name.item, diagnostics)?; + let typ = match resolved { + ResolvedName::Type(typ) => typ, + ResolvedName::ObjectName(oname) => oname.type_mark(), + ResolvedName::Overloaded(ref des, ref overloaded) => { + let disamb = self + .disambiguate_no_actuals(des, None, overloaded) + .into_eval_result(diagnostics)?; + + if let Some(disamb) = disamb { + if let Disambiguated::Unambiguous(ref ent) = disamb { + attr.name.set_unique_reference(ent); + ent.return_type().unwrap() + } else { + diagnostics.add( + attr.name.pos(self.ctx), + format!( + "{} cannot be prefix of range attribute, array type or object is required", + resolved.describe() + ), + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + } else { + return Err(EvalError::Unknown); + } + } + ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)) => typ, + ResolvedName::Expression(_) + | ResolvedName::Final(_) + | ResolvedName::Library(_) + | ResolvedName::Design(_) => { + diagnostics.add( + attr.name.pos(self.ctx), + format!( + "{} cannot be prefix of range attribute, array type or object is required", + resolved.describe() + ), + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + }; + + if let Some((_, indexes)) = typ.array_type() { + if let Some(first) = indexes.first() { + if let Some(base_type) = first { + Ok(*base_type) + } else { + // There was probably an error in the type definition of this signal/variable + Err(EvalError::Unknown) + } + } else { + // This should never happen + if let Some(decl_pos) = typ.decl_pos() { + // To debug if it ever happens + // eprintln!("{}", decl_pos.show("Array with no indexes")); + // eprintln!("{}", attr.name.pos(self.ctx).show("Used here")); + panic!("Internal error") + } + Err(EvalError::Unknown) + } + } else { + diagnostics.add( + attr.name.pos(self.ctx), + format!( + "{} cannot be prefix of range attribute, array type or object is required", + resolved.describe() + ), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } + + pub fn range_type( + &self, + scope: &Scope<'a>, + range: &mut Range, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match range { + Range::Range(ref mut constraint) => { + let left_types = + self.range_expr_type(scope, &mut constraint.left_expr, diagnostics)?; + let right_types = + self.range_expr_type(scope, &mut constraint.right_expr, diagnostics)?; + + let left_ambig = matches!(left_types, DisambiguatedType::Ambiguous(_)); + let right_ambig = matches!(right_types, DisambiguatedType::Ambiguous(_)); + + let types = match (left_types, right_types) { + (DisambiguatedType::Unambiguous(l), DisambiguatedType::Unambiguous(r)) => { + return if let Some(typ) = self.common_type(l.base(), r.base()) { + Ok(typ) + } else { + diagnostics.add( + constraint.span().pos(self.ctx), + format!( + "Range type mismatch, left is {}, right is {}", + l.base().describe(), + r.base().describe() + ), + ErrorCode::TypeMismatch, + ); + Err(EvalError::Unknown) + } + } + (DisambiguatedType::Unambiguous(l), DisambiguatedType::Ambiguous(r)) => { + self.common_types(r, l.base()) + } + + (DisambiguatedType::Ambiguous(l), DisambiguatedType::Unambiguous(r)) => { + self.common_types(l, r.base()) + } + (DisambiguatedType::Ambiguous(_), DisambiguatedType::Ambiguous(_)) => { + diagnostics.add( + constraint.span().pos(self.ctx), + "Range is ambiguous", + ErrorCode::TypeMismatch, + ); + return Err(EvalError::Unknown); + } + }; + + if types.len() == 1 { + let typ = types.into_iter().next().unwrap(); + + if left_ambig { + self.expr_with_ttyp( + scope, + typ.into(), + &mut constraint.left_expr, + diagnostics, + )?; + } + + if right_ambig { + self.expr_with_ttyp( + scope, + typ.into(), + &mut constraint.right_expr, + diagnostics, + )?; + } + + Ok(typ) + } else if types.is_empty() { + diagnostics.add( + constraint.span().pos(self.ctx), + "Range type of left and right side does not match", + ErrorCode::TypeMismatch, + ); + Err(EvalError::Unknown) + } else { + diagnostics.add( + constraint.span().pos(self.ctx), + "Range is ambiguous", + ErrorCode::TypeMismatch, + ); + Err(EvalError::Unknown) + } + } + Range::Attribute(ref mut attr) => self.range_attribute_type(scope, attr, diagnostics), + } + } + + pub fn drange_type( + &self, + scope: &Scope<'a>, + drange: &mut DiscreteRange, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let typ = match drange { + DiscreteRange::Discrete(ref mut type_mark, ref mut range) => { + let typ = self + .type_name(scope, type_mark.span, &mut type_mark.item, diagnostics)? + .base(); + if let Some(ref mut range) = range { + self.range_with_ttyp(scope, typ.into(), range, diagnostics)?; + } + typ + } + DiscreteRange::Range(ref mut range) => self.range_type(scope, range, diagnostics)?, + }; + + if typ.is_discrete() { + Ok(typ) + } else { + diagnostics.add( + drange.span().pos(self.ctx), + format!( + "Non-discrete {} cannot be used in discrete range", + typ.describe() + ), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } + + pub fn range_with_ttyp( + &self, + scope: &Scope<'a>, + target_type: TypeEnt<'a>, + range: &mut Range, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match range { + Range::Range(ref mut constraint) => { + self.expr_pos_with_ttyp( + scope, + target_type, + constraint.left_expr.span, + &mut constraint.left_expr.item, + diagnostics, + )?; + self.expr_pos_with_ttyp( + scope, + target_type, + constraint.right_expr.span, + &mut constraint.right_expr.item, + diagnostics, + )?; + } + Range::Attribute(ref mut name) => { + let AttributeName { + name, + signature, + expr, + attr, // Parser ensures this must be 'range or we would not end up here + } = name.as_mut(); + + let prefix_typ = as_fatal( + self.name_resolve(scope, name.span, &mut name.item, diagnostics) + .and_then(|prefix| { + prefix.as_type_of_attr_prefix( + self.ctx, + name.span, + &AttributeSuffix { attr, expr }, + diagnostics, + ) + }), + )?; + + if let Some(ref mut signature) = signature { + diagnostics.add( + signature.pos(self.ctx), + format!("Did not expect signature for '{attr} attribute"), + ErrorCode::UnexpectedSignature, + ); + } + + if let Some(prefix_typ) = prefix_typ { + if let Some((_, indexes)) = prefix_typ.array_type() { + if let Some(index_typ) = + as_fatal(self.array_index_expression_in_attribute( + indexes, + expr.as_mut().map(|expr| expr.as_mut()), + diagnostics, + ))? + { + if !self.can_be_target_type(index_typ.into(), target_type.base()) { + diagnostics.push(Diagnostic::type_mismatch( + &range.span().pos(self.ctx), + &index_typ.describe(), + target_type, + )) + } + } + } + } + } + } + Ok(()) + } + + pub fn drange_with_ttyp( + &self, + scope: &Scope<'a>, + target_type: TypeEnt<'a>, + drange: &mut DiscreteRange, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match drange { + DiscreteRange::Discrete(ref mut type_mark, ref mut range) => { + let _ = as_fatal(self.name_resolve( + scope, + type_mark.span, + &mut type_mark.item, + diagnostics, + ))?; + if let Some(ref mut range) = range { + self.range_with_ttyp(scope, target_type, range, diagnostics)?; + } + } + DiscreteRange::Range(ref mut range) => { + self.range_with_ttyp(scope, target_type, range, diagnostics)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::analysis::analyze::EvalError; + use crate::analysis::analyze::EvalResult; + use crate::analysis::tests::TestSetup; + use crate::ast::search::check_no_unresolved; + use crate::ast::Range; + use crate::data::error_codes::ErrorCode; + use crate::data::DiagnosticHandler; + use crate::data::NoDiagnostics; + use crate::named_entity::BaseType; + use crate::syntax::test::check_diagnostics; + use crate::syntax::test::Code; + use crate::Diagnostic; + use vhdl_lang::Token; + + impl<'a> TestSetup<'a> { + fn range_type( + &'a self, + code: &Code, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + self.ctx(&code.tokenize()) + .range_type(&self.scope, &mut code.range(), diagnostics) + } + + fn range_type_ast( + &'a self, + rng: &mut Range, + tokens: &Vec, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + self.ctx(tokens).range_type(&self.scope, rng, diagnostics) + } + } + + #[test] + fn universal_integer_range() { + let test = TestSetup::new(); + + let code = test.snippet("0 to 1"); + assert_eq!( + test.range_type(&code, &mut NoDiagnostics), + Ok(test.ctx(&code.tokenize()).universal_integer()) + ); + } + + #[test] + fn universal_integer_range_expression() { + let test = TestSetup::new(); + + let code = test.snippet("-1 to 1"); + assert_eq!( + test.range_type(&code, &mut NoDiagnostics), + Ok(test.ctx(&code.tokenize()).universal_integer()) + ); + } + + #[test] + fn character_range() { + let test = TestSetup::new(); + + let code = test.snippet("'a' to 'b'"); + assert_eq!( + test.range_type(&code, &mut NoDiagnostics), + Ok(test.lookup_type("CHARACTER").base()) + ); + } + + #[test] + fn discrete_range_not_discrete_type() { + let test = TestSetup::new(); + let code = test.snippet("0.0 to 1.0"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.ctx(&code.tokenize()).drange_type( + &test.scope, + &mut code.discrete_range(), + &mut diagnostics + ), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s1("0.0 to 1.0"), + "Non-discrete type universal_real cannot be used in discrete range", + )], + ) + } + + #[test] + fn range_not_discrete_expr() { + let test = TestSetup::new(); + let code = test.snippet("0 to (0, 0)"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.range_type(&code, &mut diagnostics), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("(0, 0)"), + "Non-scalar expression cannot be used in a range", + ErrorCode::NonScalarInRange, + )], + ) + } + + #[test] + fn range_one_side_ambiguous() { + let test = TestSetup::new(); + + test.declarative_part( + " +function f1 return character; +function f1 return integer; + ", + ); + + let code = test.snippet("f1 to 'a'"); + let mut rng = code.range(); + assert_eq!( + test.range_type_ast(&mut rng, &code.tokenize(), &mut NoDiagnostics), + Ok(test.lookup_type("CHARACTER").base()) + ); + check_no_unresolved(&mut rng, &code.tokenize()); + + let code = test.snippet("'a' to f1"); + let mut rng = code.range(); + assert_eq!( + test.range_type_ast(&mut rng, &code.tokenize(), &mut NoDiagnostics), + Ok(test.lookup_type("CHARACTER").base()) + ); + check_no_unresolved(&mut rng, &code.tokenize()); + } + + #[test] + fn range_type_mismatch_error() { + let test = TestSetup::new(); + + let code = test.snippet("0 to false"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.range_type(&code, &mut diagnostics), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("0 to false"), + "Range type mismatch, left is type universal_integer, right is type 'BOOLEAN'", + ErrorCode::TypeMismatch, + )], + ); + } + + #[test] + fn range_one_side_ambiguous_error() { + let test = TestSetup::new(); + + test.declarative_part( + " +function f1 return character; +function f1 return integer; + ", + ); + + let code = test.snippet("f1 to false"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.range_type(&code, &mut diagnostics), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("f1 to false"), + "Range type of left and right side does not match", + ErrorCode::TypeMismatch, + )], + ); + } + + #[test] + fn range_ambiguous() { + let test = TestSetup::new(); + + test.declarative_part( + " +function f1 return character; +function f1 return integer; + ", + ); + + let code = test.snippet("f1 to f1"); + let mut diagnostics = Vec::new(); + assert_eq!( + test.range_type(&code, &mut diagnostics), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("f1 to f1"), + "Range is ambiguous", + ErrorCode::TypeMismatch, + )], + ); + } + + #[test] + fn range_attribute_name() { + let test = TestSetup::new(); + + test.declarative_part( + " +type arr_t is array (integer range <>) of boolean; + +function myfun return arr_t; + + ", + ); + + let code = test.snippet("arr_t'range"); + assert_eq!( + test.range_type(&code, &mut NoDiagnostics), + Ok(test.lookup_type("integer").base()) + ); + + let code = test.snippet("myfun'range"); + assert_eq!( + test.range_type(&code, &mut NoDiagnostics), + Ok(test.lookup_type("integer").base()) + ); + + let mut diagnostics = Vec::new(); + let code = test.snippet("character'range"); + assert_eq!( + test.range_type(&code, &mut diagnostics), + Err(EvalError::Unknown) + ); + + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s1("character"), + "type 'CHARACTER' cannot be prefix of range attribute, array type or object is required", + )], + ); + } + + #[test] + fn range_attribute_name_of_access_type() { + let test = TestSetup::new(); + + test.declarative_part( + " +type arr_t is array (integer range <>) of boolean; +type ptr_t is access arr_t; +variable v : ptr_t; + ", + ); + + let code = test.snippet("v'range"); + assert_eq!( + test.range_type(&code, &mut NoDiagnostics), + Ok(test.lookup_type("integer").base()) + ); + } +} diff --git a/vhdl_lang/src/analysis/root.rs b/vhdl_lang/src/analysis/root.rs new file mode 100644 index 0000000..07ee951 --- /dev/null +++ b/vhdl_lang/src/analysis/root.rs @@ -0,0 +1,1490 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use super::analyze::*; +use super::lock::*; +use super::standard::StandardTypes; +use super::standard::UniversalTypes; +use crate::named_entity::*; + +use crate::ast::search::*; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::syntax::{Symbols, Token, TokenAccess}; +use crate::{HasTokenSpan, TokenSpan}; +use fnv::{FnvHashMap, FnvHashSet}; +use parking_lot::RwLock; +use std::collections::hash_map::Entry; +use std::ops::Deref; +use std::ops::DerefMut; +use std::sync::Arc; + +/// A design unit with design unit data +pub(crate) struct AnalysisData { + pub diagnostics: Vec, + pub has_circular_dependency: bool, + pub arena: FinalArena, +} + +pub(super) type UnitReadGuard<'a> = ReadGuard<'a, AnyDesignUnit, AnalysisData>; +pub(super) type UnitWriteGuard<'a> = WriteGuard<'a, AnyDesignUnit, AnalysisData>; + +/// Wraps the AST of a [design unit](../../ast/enum.AnyDesignUnit.html) in a thread-safe +/// r/w-lock for analysis. +pub(crate) struct LockedUnit { + ident: Ident, + arena_id: ArenaId, + unit_id: UnitId, + pub unit: AnalysisLock, + pub tokens: Vec, +} + +impl HasSrcPos for LockedUnit { + fn pos(&self) -> &SrcPos { + self.ident.pos(&self.tokens) + } +} + +impl HasUnitId for LockedUnit { + fn unit_id(&self) -> &UnitId { + &self.unit_id + } +} + +impl LockedUnit { + fn new(library_name: &Symbol, unit: AnyDesignUnit, tokens: Vec) -> LockedUnit { + let unit_id = match unit { + AnyDesignUnit::Primary(ref unit) => { + UnitId::primary(library_name, PrimaryKind::kind_of(unit), unit.name()) + } + AnyDesignUnit::Secondary(ref unit) => UnitId::secondary( + library_name, + SecondaryKind::kind_of(unit), + unit.primary_name(), + unit.name(), + ), + }; + + LockedUnit { + ident: unit.ident().clone(), + arena_id: ArenaId::default(), + unit_id, + unit: AnalysisLock::new(unit), + tokens, + } + } +} + +impl HasIdent for LockedUnit { + fn ident(&self) -> &Ident { + &self.ident + } +} + +/// Represents a VHDL library containing zero or more design units. +/// +/// This struct also keeps track of which source file contained which design units. +pub struct Library { + name: Symbol, + + /// Arena is only used to store the AnyEnt for the library itself + /// as there is no other good place to store it + arena: FinalArena, + id: EntityId, + + units: FnvHashMap, + units_by_source: FnvHashMap>, + + /// Units removed since last analysis. + removed: FnvHashSet, + /// Units added since last analysis. + added: FnvHashSet, + + /// Design units which were not added since they were duplicates. + /// They need to be kept for later refresh which might make them not duplicates. + duplicates: Vec<(SrcPos, LockedUnit)>, +} + +impl Library { + fn new(name: Symbol) -> Library { + let arena = Arena::new(ArenaId::default()); + + let ent = arena.alloc( + Designator::Identifier(name.clone()), + None, + Related::None, + AnyEntKind::Library, + None, + TokenSpan::for_library(), + None, + ); + + Library { + name, + id: ent.id(), + arena: arena.finalize(), + units: FnvHashMap::default(), + units_by_source: FnvHashMap::default(), + added: FnvHashSet::default(), + removed: FnvHashSet::default(), + duplicates: Vec::new(), + } + } + + pub fn name(&self) -> &Symbol { + &self.name + } + + fn add_design_unit(&mut self, unit: LockedUnit) { + let unit_id = unit.unit_id().clone(); + match self.units.entry(unit.key().clone()) { + Entry::Occupied(entry) => { + self.duplicates + .push((entry.get().ident().pos(&entry.get().tokens).clone(), unit)); + } + Entry::Vacant(entry) => { + self.added.insert(unit_id); + match self.units_by_source.entry(unit.source().clone()) { + Entry::Occupied(mut entry) => { + entry.get_mut().insert(unit.unit_id().clone()); + } + Entry::Vacant(entry) => { + let mut set = FnvHashSet::default(); + set.insert(unit.unit_id().clone()); + entry.insert(set); + } + } + entry.insert(unit); + } + } + } + + fn add_design_file(&mut self, design_file: DesignFile) { + for (tokens, design_unit) in design_file.design_units { + self.add_design_unit(LockedUnit::new(self.name(), design_unit, tokens)); + } + } + + /// Refresh library after removing or adding new design units. + fn refresh(&mut self, diagnostics: &mut dyn DiagnosticHandler) { + self.append_duplicate_diagnostics(diagnostics); + } + + fn append_duplicate_diagnostics(&self, diagnostics: &mut dyn DiagnosticHandler) { + for (prev_pos, unit) in self.duplicates.iter() { + let tokens = &unit.tokens; + let diagnostic = match unit.key() { + UnitKey::Primary(ref primary_name) => Diagnostic::new( + unit.ident_pos(tokens), + format!( + "A primary unit has already been declared with name '{}' in library '{}'", + primary_name, &self.name + ), + ErrorCode::Duplicate, + ), + UnitKey::Secondary(ref primary_name, ref name) => match unit.kind() { + AnyKind::Secondary(SecondaryKind::Architecture) => Diagnostic::new( + unit.ident_pos(tokens), + format!("Duplicate architecture '{name}' of entity '{primary_name}'",), + ErrorCode::Duplicate, + ), + AnyKind::Secondary(SecondaryKind::PackageBody) => Diagnostic::new( + unit.ident_pos(tokens), + format!("Duplicate package body of package '{primary_name}'"), + ErrorCode::Duplicate, + ), + AnyKind::Primary(_) => { + unreachable!(); + } + }, + }; + + let diagnostic = diagnostic.related(prev_pos, "Previously defined here"); + diagnostics.push(diagnostic); + } + } + + /// Remove all design units defined in source. + /// This is used for incremental analysis where only a single source file is updated. + fn remove_source(&mut self, source: &Source) { + let removed = &mut self.removed; + self.units.retain(|_, value| { + if value.source() != source { + true + } else { + removed.insert(value.unit_id().clone()); + false + } + }); + self.units_by_source.remove(source); + self.duplicates + .retain(|(_, value)| value.source() != source); + + // Try to add duplicates that were duplicated by a design unit in the removed file + let num_duplicates = self.duplicates.len(); + let duplicates = + std::mem::replace(&mut self.duplicates, Vec::with_capacity(num_duplicates)); + for (prev_pos, design_unit) in duplicates.into_iter() { + if prev_pos.source() == source { + self.add_design_unit(design_unit); + } else { + self.duplicates.push((prev_pos, design_unit)); + } + } + } + + /// Iterate over units in the order they appear in the file. + /// Ensures diagnostics do not have to be sorted later. + fn sorted_unit_ids(&self) -> Vec { + // @TODO insert sort when adding instead + let mut result = Vec::new(); + + for unit_ids in self.units_by_source.values() { + let mut unit_ids: Vec = unit_ids.clone().into_iter().collect(); + unit_ids.sort_by_key(|unit_id| self.units.get(unit_id.key()).unwrap().pos().start()); + result.append(&mut unit_ids); + } + result + } + + pub(crate) fn get_unit(&self, key: &UnitKey) -> Option<&LockedUnit> { + self.units.get(key) + } + + pub fn id(&self) -> EntityId { + self.id + } + + pub(crate) fn units(&self) -> impl Iterator { + self.units.values() + } + + pub(crate) fn primary_units(&self) -> impl Iterator { + self.units.iter().filter_map(|(key, value)| match key { + UnitKey::Primary(_) => Some(value), + UnitKey::Secondary(_, _) => None, + }) + } + + pub(crate) fn secondary_units<'a>( + &'a self, + primary: &'a Symbol, + ) -> impl Iterator { + self.units.iter().filter_map(move |(key, value)| match key { + UnitKey::Secondary(sym, _) if primary == sym => Some(value), + _ => None, + }) + } + + pub(crate) fn primary_unit(&self, symbol: &Symbol) -> Option<&LockedUnit> { + self.units.get(&UnitKey::Primary(symbol.clone())) + } +} + +/// Contains the entire design state. +/// +/// Besides all loaded libraries and design units, `DesignRoot` also keeps track of +/// dependencies between design units. +pub struct DesignRoot { + pub(super) symbols: Arc, + pub(super) standard_pkg_id: Option, + pub(super) standard_arena: Option, + pub(super) universal: Option, + pub(super) standard_types: Option, + pub(super) std_ulogic: Option, + libraries: FnvHashMap, + + // Arena storage of all declaration in the design + pub(super) arenas: FinalArena, + + // Dependency tracking for incremental analysis. + // user => set(users) + users_of: RwLock>>, + + // missing unit name => set(affected) + #[allow(clippy::type_complexity)] + missing_unit: RwLock), FnvHashSet>>, + + // Tracks which units have a "use library.all;" clause. + // library name => set(affected) + users_of_library_all: RwLock>>, +} + +impl DesignRoot { + pub fn new(symbols: Arc) -> DesignRoot { + DesignRoot { + universal: None, + standard_pkg_id: None, + standard_arena: None, + standard_types: None, + std_ulogic: None, + symbols, + arenas: FinalArena::default(), + libraries: FnvHashMap::default(), + users_of: RwLock::new(FnvHashMap::default()), + missing_unit: RwLock::new(FnvHashMap::default()), + users_of_library_all: RwLock::new(FnvHashMap::default()), + } + } + + /// Create library if it does not exist or return existing + fn get_or_create_library(&mut self, name: Symbol) -> &mut Library { + match self.libraries.entry(name) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let name = entry.key().clone(); + let library = Library::new(name); + entry.insert(library) + } + } + } + + pub fn ensure_library(&mut self, name: Symbol) { + self.get_or_create_library(name); + } + + pub(super) fn get_library_units( + &self, + library_name: &Symbol, + ) -> Option<&FnvHashMap> { + self.libraries + .get(library_name) + .map(|library| &library.units) + } + + /// Iterates over all available library symbols. + pub fn available_libraries(&self) -> impl Iterator { + self.libraries.keys() + } + + pub fn libraries(&self) -> impl Iterator { + self.libraries.values() + } + + pub fn get_lib(&self, sym: &Symbol) -> Option<&Library> { + self.libraries.get(sym) + } + + pub(crate) fn get_design_entity<'a>( + &'a self, + library_name: &Symbol, + ident: &Symbol, + ) -> Option> { + let units = self.get_library_units(library_name)?; + let unit = units.get(&UnitKey::Primary(ident.clone()))?; + let data = self.get_analysis(unit); + + if let AnyDesignUnit::Primary(primary) = data.deref() { + if let Some(id) = primary.ent_id() { + let design = DesignEnt::from_any(self.arenas.get(id))?; + if matches!(design.kind(), Design::Entity(..)) { + return Some(design); + } + } + } + None + } + + /// Get a named entity corresponding to the library + pub(super) fn get_library_arena( + &self, + library_name: &Symbol, + ) -> Option<(&FinalArena, EntityId)> { + self.libraries + .get(library_name) + .map(|library| (&library.arena, library.id)) + } + + pub fn add_design_file(&mut self, library_name: Symbol, design_file: DesignFile) { + self.get_or_create_library(library_name) + .add_design_file(design_file); + } + + pub fn remove_source(&mut self, library_name: Symbol, source: &Source) { + self.get_or_create_library(library_name) + .remove_source(source); + } + + /// Search for reference at position + /// Character offset on a line in a document (zero-based). Assuming that the line is + /// represented as a string, the `character` value represents the gap between the + /// `character` and `character + 1`. + /// + /// If the character value is greater than the line length it defaults back to the + /// line length. + pub fn item_at_cursor( + &self, + source: &Source, + cursor: Position, + ) -> Option<(SrcPos, EntRef<'_>)> { + let mut searcher = ItemAtCursor::new(self, cursor); + + for unit in self.units_by_source(source) { + let _ = unit + .unit + .expect_analyzed() + .search(&unit.tokens, &mut searcher); + + if searcher.result.is_some() { + return searcher.result; + } + } + + None + } + + pub fn search_reference(&self, source: &Source, cursor: Position) -> Option> { + let (_, ent) = self.item_at_cursor(source, cursor)?; + Some(ent) + } + + pub fn find_definition_of<'a>(&'a self, decl: EntRef<'a>) -> Option> { + if decl.is_protected_type() + || decl.is_subprogram_decl() + || decl.kind().is_deferred_constant() + { + let mut searcher = FindEnt::new(self, |ent| ent.is_declared_by(decl)); + let _ = self.search(&mut searcher); + + Some(searcher.result.unwrap_or(decl)) + } else { + // The definition is the same as the declaration + Some(decl) + } + } + + pub fn find_implementation<'a>(&'a self, ent: EntRef<'a>) -> Vec> { + if let Designator::Identifier(ident) = ent.designator() { + if let Some(library_name) = ent.library_name() { + match ent.kind() { + // Find entity with same name as component in the library + AnyEntKind::Component(_) => { + if let Some(design) = self.get_design_entity(library_name, ident) { + return vec![design.into()]; + } + } + // Find components and architectures to entity + AnyEntKind::Design(Design::Entity(..)) => { + let ent_id = ent.id; + let mut searcher = FindAllEnt::new(self, |ent| match ent.kind() { + // Find all components with same name as entity in the library + AnyEntKind::Component(_) => { + matches!( + ent.designator(), + Designator::Identifier(comp_ident) if comp_ident == ident + ) + } + // Find all architectures which implement the entity + AnyEntKind::Design(Design::Architecture(.., ent_of_arch)) => { + ent_of_arch.id == ent_id + } + _ => false, + }); + + let _ = self.search_library(library_name, &mut searcher); + return searcher.result; + } + _ => {} + } + } + } + Vec::default() + } + + #[cfg(test)] + pub fn search_reference_pos(&self, source: &Source, cursor: Position) -> Option { + self.search_reference(source, cursor) + .and_then(|ent| ent.decl_pos().cloned()) + } + /// Search for the declaration at decl_pos and format it + pub fn format_declaration(&self, ent: EntRef<'_>) -> Option { + if let AnyEntKind::Library = ent.kind() { + Some(format!("library {};", ent.designator())) + } else { + let ent = if let Related::InstanceOf(ent) = ent.related { + ent + } else { + ent + }; + + let mut searcher = FormatDeclaration::new(ent); + let _ = self.search(&mut searcher); + searcher.result + } + } + + /// Search for all references to the declaration at decl_pos + pub fn find_all_references(&self, ent: EntRef<'_>) -> Vec { + let mut searcher = FindAllReferences::new(self, ent); + let _ = self.search(&mut searcher); + searcher.references + } + + pub fn find_all_references_in_source(&self, source: &Source, ent: EntRef<'_>) -> Vec { + let mut searcher = FindAllReferences::new(self, ent); + let _ = self.search_source(source, &mut searcher); + searcher.references + } + + pub fn public_symbols<'a>(&'a self) -> Box> + 'a> { + Box::new(self.libraries.values().flat_map(|library| { + std::iter::once(self.arenas.get(library.id)).chain(library.units.values().flat_map( + |unit| -> Box>> { + if matches!(unit.kind(), AnyKind::Primary(_)) { + let data = self.get_analysis(unit); + if let AnyDesignUnit::Primary(primary) = data.deref() { + if let Some(id) = primary.ent_id() { + let ent = self.arenas.get(id); + return Box::new(std::iter::once(ent).chain(public_symbols(ent))); + } + } + } else if matches!(unit.kind(), AnyKind::Secondary(SecondaryKind::Architecture)) + { + let data = self.get_analysis(unit); + if let AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture(arch)) = + data.deref() + { + if let Some(id) = arch.ident.decl.get() { + let ent = self.arenas.get(id); + return Box::new(std::iter::once(ent)); + } + } + } else if matches!(unit.kind(), AnyKind::Secondary(SecondaryKind::PackageBody)) + { + let data = self.get_analysis(unit); + if let AnyDesignUnit::Secondary(AnySecondaryUnit::PackageBody(body)) = + data.deref() + { + if let Some(id) = body.ident.decl.get() { + let ent = self.arenas.get(id); + return Box::new(std::iter::once(ent)); + } + } + } + Box::new(std::iter::empty()) + }, + )) + })) + } + + pub fn document_symbols<'a>( + &'a self, + library_name: &Symbol, + source: &Source, + ) -> Vec<(EntHierarchy<'a>, &'a Vec)> { + let Some(library) = self.libraries.get(library_name) else { + return vec![]; + }; + + let Some(unit_ids) = library.units_by_source.get(source) else { + return vec![]; + }; + + let mut result = Vec::new(); + + for unit_id in unit_ids { + let locked_unit = library.units.get(unit_id.key()).unwrap(); + let unit = locked_unit.unit.expect_analyzed(); + let Some(ent_id) = unit.data().ent_id() else { + continue; + }; + let primary_ent = self.get_ent(ent_id); + + let mut searcher = FindAllEnt::new(self, |ent| ent.is_explicit()); + let _ = unit.search(&locked_unit.tokens, &mut searcher); + searcher.result.sort_by_key(|ent| ent.src_span.start_token); + let hierarchy = EntHierarchy::from_parent(primary_ent, searcher.result); + result.push((hierarchy, &locked_unit.tokens)) + } + result.sort_by_key(|(hierarchy, _)| &hierarchy.ent.decl_pos); + result + } + + pub fn find_all_unresolved(&self) -> (usize, Vec) { + let mut searcher = FindAllUnresolved::default(); + let _ = self.search(&mut searcher); + (searcher.count, searcher.unresolved) + } + + #[cfg(test)] + pub fn find_all_references_pos(&self, decl_pos: &SrcPos) -> Vec { + if let Some(ent) = self.search_reference(decl_pos.source(), decl_pos.start()) { + self.find_all_references(ent) + } else { + Vec::new() + } + } + + #[cfg(test)] + fn find_std_package(&self, symbol: &str) -> EntRef<'_> { + let std_lib = self.libraries.get(&self.symbol_utf8("std")).unwrap(); + let unit = std_lib + .get_unit(&UnitKey::Primary(self.symbol_utf8(symbol))) + .unwrap(); + if let AnyPrimaryUnit::Package(pkg) = unit.unit.write().as_primary_mut().unwrap() { + self.get_ent(pkg.ident.decl.expect_defined()) + } else { + panic!("Not a package"); + } + } + + #[cfg(test)] + pub fn find_standard_pkg(&self) -> EntRef<'_> { + self.find_std_package("standard") + } + + #[cfg(test)] + pub fn find_textio_pkg(&self) -> EntRef<'_> { + self.find_std_package("textio") + } + + #[cfg(test)] + pub fn find_env_pkg(&self) -> EntRef<'_> { + self.find_std_package("env") + } + + #[cfg(test)] + pub fn find_standard_symbol(&self, name: &str) -> EntRef<'_> { + self.find_std_symbol("standard", name) + } + + #[cfg(test)] + pub fn find_env_symbol(&self, name: &str) -> EntRef<'_> { + self.find_std_symbol("env", name) + } + + #[cfg(test)] + pub fn find_overloaded_env_symbols(&self, name: &str) -> &NamedEntities<'_> { + self.find_std_symbols("env", name) + } + + #[cfg(test)] + fn find_std_symbol(&self, package: &str, name: &str) -> EntRef<'_> { + if let AnyEntKind::Design(Design::Package(_, region)) = + self.find_std_package(package).kind() + { + let sym = region.lookup_immediate(&Designator::Identifier(self.symbol_utf8(name))); + sym.unwrap().first() + } else { + panic!("Not a package"); + } + } + + #[cfg(test)] + fn find_std_symbols(&self, package: &str, name: &str) -> &NamedEntities<'_> { + if let AnyEntKind::Design(Design::Package(_, region)) = + self.find_std_package(package).kind() + { + let sym = region.lookup_immediate(&Designator::Identifier(self.symbol_utf8(name))); + sym.unwrap() + } else { + panic!("Not a package"); + } + } + + pub fn search(&self, searcher: &mut impl Searcher) -> SearchResult { + for library in self.libraries.values() { + for unit_id in library.sorted_unit_ids() { + let unit = library.units.get(unit_id.key()).unwrap(); + return_if_found!(unit.unit.expect_analyzed().search(&unit.tokens, searcher)); + } + } + NotFound + } + + fn units_by_source<'a>( + &'a self, + source: &'a Source, + ) -> impl Iterator + 'a { + self.libraries() + .flat_map(|lib| { + lib.units_by_source + .get(source) + .map(|unit_ids| (lib, unit_ids)) + }) + .flat_map(|(lib, units_ids)| { + units_ids + .iter() + .filter_map(|unit_id| lib.get_unit(unit_id.key())) + }) + } + + /// Search all units in a source file denoted by `source`. + pub fn search_source(&self, source: &Source, searcher: &mut impl Searcher) -> SearchResult { + for unit in self.units_by_source(source) { + return_if_found!(unit.unit.expect_analyzed().search(&unit.tokens, searcher)); + } + NotFound + } + + pub fn search_library( + &self, + library_name: &Symbol, + searcher: &mut impl Searcher, + ) -> SearchResult { + if let Some(library) = self.libraries.get(library_name) { + for unit_id in library.sorted_unit_ids() { + let unit = library.units.get(unit_id.key()).unwrap(); + return_if_found!(unit.unit.expect_analyzed().search(&unit.tokens, searcher)); + } + } + NotFound + } + + pub fn symbol_utf8(&self, name: &str) -> Symbol { + self.symbols.symtab().insert_utf8(name) + } + + fn analyze_unit( + &self, + arena_id: ArenaId, + unit_id: &UnitId, + source: Source, + unit: &mut UnitWriteGuard<'_>, + ctx: &dyn TokenAccess, + ) { + // All units reference the standard arena + // @TODO keep the same ArenaId when re-using unit + let arena = Arena::new(arena_id); + let context = AnalyzeContext::new(self, unit_id, source, &arena, ctx); + + let mut diagnostics = Vec::new(); + let mut has_circular_dependency = false; + + let result = match unit.deref_mut() { + AnyDesignUnit::Primary(unit) => { + if let Err(err) = context.analyze_primary_unit(unit, &mut diagnostics) { + has_circular_dependency = true; + err.push_into(&mut diagnostics); + }; + + AnalysisData { + arena: arena.finalize(), + diagnostics, + has_circular_dependency, + } + } + + AnyDesignUnit::Secondary(unit) => { + let mut diagnostics = Vec::new(); + + if let Err(err) = context.analyze_secondary_unit(unit, &mut diagnostics) { + has_circular_dependency = true; + err.push_into(&mut diagnostics); + }; + + AnalysisData { + arena: arena.finalize(), + diagnostics, + has_circular_dependency, + } + } + }; + + unit.finish(result); + } + + pub(super) fn get_analysis<'a>(&self, locked_unit: &'a LockedUnit) -> UnitReadGuard<'a> { + match locked_unit.unit.entry() { + AnalysisEntry::Vacant(mut unit) => { + self.analyze_unit( + locked_unit.arena_id, + locked_unit.unit_id(), + locked_unit.pos().source().clone(), + &mut unit, + &locked_unit.tokens, + ); + unit.downgrade() + } + AnalysisEntry::Occupied(unit) => unit, + } + } + + pub(super) fn get_unit(&self, unit_id: &UnitId) -> Option<&LockedUnit> { + self.libraries + .get(unit_id.library_name()) + .and_then(|library| library.units.get(unit_id.key())) + } + + fn reset_affected(&self, mut affected: FnvHashSet) { + // Reset analysis state of all design units + for unit_id in affected.drain() { + if let Some(unit) = self.get_unit(&unit_id) { + unit.unit.reset(); + + // Ensure no remaining references from previous analysis + clear_references(unit.unit.write().deref_mut(), &unit.tokens); + } + } + } + + /// Register a direct dependency between two library units + pub(super) fn make_use_of( + &self, + use_pos: Option<&SrcPos>, + user: &UnitId, + unit_id: &UnitId, + ) -> FatalResult { + let mut users_of = self.users_of.write(); + match users_of.entry(unit_id.clone()) { + Entry::Occupied(mut entry) => { + entry.get_mut().insert(user.clone()); + } + Entry::Vacant(entry) => { + let mut set = FnvHashSet::default(); + set.insert(user.clone()); + entry.insert(set); + } + } + + let mut affected = FnvHashSet::default(); + affected.insert(user.clone()); + let all_affected = get_all_affected(&users_of, affected); + + if all_affected.contains(unit_id) { + Err(CircularDependencyError::new(use_pos)) + } else { + Ok(()) + } + } + + /// Register a dependency of library unit for everything within library since .all was used + pub(super) fn make_use_of_library_all(&self, user: &UnitId, library_name: &Symbol) { + match self + .users_of_library_all + .write() + .entry(library_name.clone()) + { + Entry::Occupied(mut entry) => { + entry.get_mut().insert(user.clone()); + } + Entry::Vacant(entry) => { + let mut set = FnvHashSet::default(); + set.insert(user.clone()); + entry.insert(set); + } + } + } + + /// Make use of a missing unit name. The library unit will be sensitive to adding such a unit in the future. + pub(super) fn make_use_of_missing_unit( + &self, + user: &UnitId, + library_name: &Symbol, + primary_name: &Symbol, + secondary_name: Option<&Symbol>, + ) { + let mut missing_unit = self.missing_unit.write(); + let key = ( + library_name.clone(), + primary_name.clone(), + secondary_name.cloned(), + ); + match missing_unit.entry(key) { + Entry::Occupied(mut entry) => { + entry.get_mut().insert(user.clone()); + } + Entry::Vacant(entry) => { + let mut set = FnvHashSet::default(); + set.insert(user.clone()); + entry.insert(set); + } + } + } + + /// Resets the analysis state of all design units which need to be re-analyzed + /// because another design unit has been added or removed. + fn reset(&mut self) { + let mut removed = FnvHashSet::default(); + let mut added = FnvHashSet::default(); + + for library in self.libraries.values_mut() { + for unit_id in library.added.drain() { + added.insert(unit_id); + } + for unit_id in library.removed.drain() { + removed.insert(unit_id); + } + } + + let mut affected: FnvHashSet<_> = added.union(&removed).cloned().collect(); + let changed: FnvHashSet<_> = removed.intersection(&added).cloned().collect(); + removed = removed.difference(&changed).cloned().collect(); + added = added.difference(&changed).cloned().collect(); + + let users_of = self.users_of.read(); + let users_of_library_all = self.users_of_library_all.read(); + + // Add affected users which do 'use library.all' + for unit_id in removed.iter().chain(added.iter()) { + if let Some(library_all_affected) = users_of_library_all.get(unit_id.library_name()) { + for user in library_all_affected.iter() { + affected.insert(user.clone()); + } + } + } + let missing_unit = self.missing_unit.read(); + for ((library_name, primary_name, secondary_name), unit_ids) in missing_unit.iter() { + let was_added = added.iter().any(|added_id| { + added_id.library_name() == library_name + && added_id.primary_name() == primary_name + && added_id.secondary_name() == secondary_name.as_ref() + }); + + if was_added { + for unit_id in unit_ids.iter() { + affected.insert(unit_id.clone()); + } + } + } + + // Affect packages which have got body removed or added + // Since a package without body may not have deferred constants + for unit_id in added.iter().chain(removed.iter()) { + if let AnyKind::Secondary(SecondaryKind::PackageBody) = unit_id.kind() { + affected.insert(UnitId::package( + unit_id.library_name(), + unit_id.primary_name(), + )); + } + } + + self.reset_affected(get_all_affected(&users_of, affected)); + drop(users_of); + drop(users_of_library_all); + drop(missing_unit); + + let mut users_of = self.users_of.write(); + let mut users_of_library_all = self.users_of_library_all.write(); + let mut missing_unit = self.missing_unit.write(); + + // Clean-up after removed units + for removed_unit in removed.iter() { + users_of.remove(removed_unit); + if let Some(library_all_affected) = + users_of_library_all.get_mut(removed_unit.library_name()) + { + library_all_affected.remove(removed_unit); + } + + missing_unit.retain(|_, unit_ids| { + unit_ids.remove(removed_unit); + !unit_ids.is_empty() + }); + } + } + + fn analyze_standard_package(&mut self) { + // Analyze standard package first if it exits + let std_lib_name = self.symbol_utf8("std"); + let Some(standard_units) = self + .libraries + .get(&std_lib_name) + .map(|library| &library.units) + else { + return; + }; + let Some(locked_unit) = standard_units.get(&UnitKey::Primary(self.symbol_utf8("standard"))) + else { + return; + }; + let AnalysisEntry::Vacant(mut unit) = locked_unit.unit.entry() else { + return; + }; + // Clear to ensure the analysis of standard package does not believe it has the standard package + let arena = Arena::new_std(); + self.standard_pkg_id = None; + self.standard_arena = None; + + let std_package = if let Some(AnyPrimaryUnit::Package(pkg)) = unit.as_primary_mut() { + assert!(pkg.context_clause.is_empty()); + assert!(pkg.generic_clause.is_none()); + pkg + } else { + panic!("Expected standard package is primary unit"); + }; + + let standard_pkg = { + let (lib_arena, id) = self.get_library_arena(&std_lib_name).unwrap(); + arena.link(lib_arena); + let std_lib = arena.get(id); + + arena.explicit( + self.symbol_utf8("standard"), + std_lib, + // Will be overwritten below + AnyEntKind::Design(Design::Package(Visibility::default(), Region::default())), + Some(std_package.ident_pos(&locked_unit.tokens)), + std_package.span(), + Some(locked_unit.source().clone()), + ) + }; + + std_package.ident.decl.set(standard_pkg.id()); + + let universal = UniversalTypes::new(&arena, standard_pkg, self.symbols.as_ref()); + self.universal = Some(universal); + + // Reserve space in the arena for the standard types + self.standard_types = Some(StandardTypes::new( + &locked_unit.tokens, + &arena, + standard_pkg, + &mut std_package.decl, + )); + + let context = AnalyzeContext::new( + self, + locked_unit.unit_id(), + locked_unit.source().clone(), + &arena, + &locked_unit.tokens, + ); + + let mut diagnostics = Vec::new(); + let root_scope = Scope::default(); + let scope = root_scope.nested().in_package_declaration(); + + { + for ent in context + .universal_implicits(UniversalType::Integer, context.universal_integer().into()) + { + unsafe { + arena.add_implicit(universal.integer, ent); + }; + scope.add(ent, &mut diagnostics); + } + + for ent in + context.universal_implicits(UniversalType::Real, context.universal_real().into()) + { + unsafe { + arena.add_implicit(universal.real, ent); + }; + scope.add(ent, &mut diagnostics); + } + } + + for decl in std_package.decl.iter_mut() { + if let Declaration::Type(ref mut type_decl) = decl.item { + context + .analyze_type_declaration( + &scope, + standard_pkg, + type_decl, + type_decl.ident.decl.get(), // Set by standard types + &mut diagnostics, + ) + .unwrap(); + } else { + context + .analyze_declaration(&scope, standard_pkg, decl, &mut diagnostics) + .unwrap(); + } + } + scope.close(&mut diagnostics); + + let mut region = scope.into_region(); + + context.end_of_package_implicits(&mut region, &mut diagnostics); + let visibility = root_scope.into_visibility(); + + let kind = AnyEntKind::Design(Design::Package(visibility, region)); + unsafe { + standard_pkg.set_kind(kind); + } + + self.standard_pkg_id = Some(standard_pkg.id()); + let arena = arena.finalize(); + self.standard_arena = Some(arena.clone()); + + let result = AnalysisData { + arena, + diagnostics, + has_circular_dependency: false, + }; + + unit.finish(result); + } + + /// Analyze ieee std_logic_1164 package library sequentially + /// The matching operators such as ?= are implicitly defined for arrays with std_ulogic element + /// So these types are blessed by the language and we need global access to them + fn analyze_std_logic_1164(&mut self) { + if let Some(lib) = self.libraries.get(&self.symbol_utf8("ieee")) { + if let Some(unit) = lib.get_unit(&UnitKey::Primary(self.symbol_utf8("std_logic_1164"))) + { + let data = self.get_analysis(unit); + let std_logic_arena = &data.result().arena; + if let AnyDesignUnit::Primary(primary) = data.deref() { + if let Some(ent) = primary.ent_id() { + let AnyEntKind::Design(Design::Package(_, ref region)) = + std_logic_arena.get(ent).kind() + else { + unreachable!() + }; + + if let Some(NamedEntities::Single(ent)) = region.lookup_immediate( + &Designator::Identifier(self.symbol_utf8("std_ulogic")), + ) { + self.std_ulogic = Some(ent.id()); + } + + self.arenas.link(&data.result().arena); + } + } + } + } + } + + // Returns the units that where re-analyzed + pub fn analyze(&mut self, diagnostics: &mut dyn DiagnosticHandler) -> Vec { + self.reset(); + + let mut units = Vec::default(); + for library in self.libraries.values() { + for unit in library.units.values() { + if !unit.unit.is_analyzed() { + units.push(unit.unit_id().clone()); + } + } + } + + for library in self.libraries.values_mut() { + library.refresh(diagnostics); + } + + // Rebuild declaration arenas of named entities + self.arenas.clear(); + + // Analyze standard package first sequentially since everything else in the + // language depends on it, and we want to save a reference to all types there + self.analyze_standard_package(); + + if let Some(std_arena) = self.standard_arena.as_ref() { + // @TODO some project.rs unit tests do not have the standard package + self.arenas.link(std_arena); + } + + self.analyze_std_logic_1164(); + + use rayon::prelude::*; + + units.par_iter().for_each(|id| { + self.get_analysis(self.get_unit(id).unwrap()); + }); + + for library in self.libraries.values() { + self.arenas.link(&library.arena); + for unit in library.units.values() { + if let Some(result) = unit.unit.get() { + self.arenas.link(&result.result().arena); + } + } + } + + // Emit diagnostics sorted within a file + for library in self.libraries.values() { + for unit_id in library.sorted_unit_ids() { + let unit = library.units.get(unit_id.key()).unwrap(); + diagnostics.append(unit.unit.expect_analyzed().result().diagnostics.clone()); + } + } + + units + } + + /// Get the named entity + pub fn get_ent(&self, id: EntityId) -> EntRef<'_> { + self.arenas.get(id) + } + + /// Returns a reference to the symbols that were used to analyze and parse the design root. + pub fn symbols(&self) -> &Symbols { + self.symbols.as_ref() + } + + /// Gets an entity-ID from a raw `usize` value and checks that the entity ID is + /// valid, i.e., points to an existing [AnyEnt]. + pub fn entity_id_from_raw(&self, raw: usize) -> Option { + let id = EntityId::from_raw(raw); + if self.arenas.is_valid_id(id) { + Some(id) + } else { + None + } + } +} + +fn get_all_affected( + users_of: &FnvHashMap>, + mut affected: FnvHashSet, +) -> FnvHashSet { + let mut all_affected = FnvHashSet::default(); + let mut next_affected = FnvHashSet::default(); + + while !affected.is_empty() { + for user in affected.drain() { + all_affected.insert(user.clone()); + + if let Some(users) = users_of.get(&user) { + for new_user in users.iter() { + if all_affected.insert(new_user.clone()) { + next_affected.insert(new_user.clone()); + } + } + } + } + + affected = std::mem::replace(&mut next_affected, affected); + } + all_affected +} + +pub struct EntHierarchy<'a> { + pub ent: EntRef<'a>, + pub children: Vec>, +} + +impl<'a> EntHierarchy<'a> { + fn from_parent(parent: EntRef<'a>, mut symbols: Vec>) -> EntHierarchy<'a> { + let mut by_parent: FnvHashMap>> = Default::default(); + + symbols.retain(|ent| { + if let Some(parent) = ent.parent_in_same_source() { + by_parent.entry(parent.id()).or_default().push(ent); + false + } else { + true + } + }); + Self::from_ent(parent, &by_parent) + } + + fn from_ent( + ent: EntRef<'a>, + by_parent: &FnvHashMap>>, + ) -> EntHierarchy<'a> { + EntHierarchy { + ent, + children: if let Some(children) = by_parent.get(&ent.id()) { + children + .iter() + .map(|ent| Self::from_ent(ent, by_parent)) + .collect() + } else { + Vec::new() + }, + } + } + + pub fn into_flat(self) -> Vec> { + std::iter::once(self.ent) + .chain( + self.children + .into_iter() + .flat_map(|ent| ent.into_flat().into_iter()), + ) + .collect() + } +} + +fn public_symbols<'a>(ent: EntRef<'a>) -> Box> + 'a> { + match ent.kind() { + AnyEntKind::Design( + Design::Entity(_, region) + | Design::Package(_, region) + | Design::UninstPackage(_, region), + ) => Box::new( + region + .immediates() + .flat_map(|ent| std::iter::once(ent).chain(public_symbols(ent))), + ), + AnyEntKind::Type(t) => match t { + Type::Protected(region, is_body) if !is_body => Box::new(region.immediates()), + _ => Box::new(std::iter::empty()), + }, + _ => Box::new(std::iter::empty()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; + use crate::syntax::test::{check_diagnostics, Code}; + + fn new_library_with_diagnostics(code: &Code, name: &str) -> (Library, Vec) { + let mut diagnostics = Vec::new(); + let mut library = Library::new(code.symbol(name)); + library.add_design_file(code.design_file()); + library.refresh(&mut diagnostics); + (library, diagnostics) + } + + #[test] + fn error_on_duplicate_package_body() { + let code = Code::new( + " +package pkg is +end package; + +package body pkg is +end package body; + +package body pkg is +end package body; +", + ); + let (library, diagnostics) = new_library_with_diagnostics(&code, "libname"); + + assert_eq!(library.units.len(), 2); + assert_eq!(library.duplicates.len(), 1); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("pkg", 3), + "Duplicate package body of package 'pkg'", + ErrorCode::Duplicate, + ) + .related(code.s("pkg", 2), "Previously defined here")], + ); + } + + #[test] + fn error_on_duplicate_primary_unit() { + let code = Code::new( + " +package pkg is +end package; + +entity pkg is +end entity; + +entity entname is +end entity; + +package entname is +end package; + +configuration pkg of entname is + for rtl + end for; +end configuration; + +package pkg is new gpkg generic map (const => foo); +", + ); + let (library, diagnostics) = new_library_with_diagnostics(&code, "libname"); + + assert_eq!(library.units.len(), 2); + assert_eq!(library.duplicates.len(), 4); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg", 2), + "A primary unit has already been declared with name 'pkg' in library 'libname'", + ErrorCode::Duplicate, + ).related(code.s("pkg", 1), "Previously defined here"), + Diagnostic::new( + code.s("entname", 2), + "A primary unit has already been declared with name 'entname' in library 'libname'", + ErrorCode::Duplicate, + ).related(code.s("entname", 1), "Previously defined here"), + Diagnostic::new( + code.s("pkg", 3), + "A primary unit has already been declared with name 'pkg' in library 'libname'", + ErrorCode::Duplicate, + ).related(code.s("pkg", 1), "Previously defined here"), + Diagnostic::new( + code.s("pkg", 4), + "A primary unit has already been declared with name 'pkg' in library 'libname'", + ErrorCode::Duplicate, + ).related(code.s("pkg", 1), "Previously defined here"), + ], + ); + } + + #[test] + fn error_on_duplicate_architecture() { + let code = Code::new( + " +entity ent is +end ent; + +architecture rtl of ent is +begin +end architecture; + +architecture rtl of ent is +begin +end architecture; +", + ); + let (library, diagnostics) = new_library_with_diagnostics(&code, "libname"); + + assert_eq!(library.units.len(), 2); + assert_eq!(library.duplicates.len(), 1); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("rtl", 2), + "Duplicate architecture 'rtl' of entity 'ent'", + ErrorCode::Duplicate, + ) + .related(code.s("rtl", 1), "Previously defined here")], + ); + } + + #[test] + fn error_on_duplicate_configuration() { + let code = Code::new( + " +entity ent is +end entity; + +configuration cfg of ent is + for rtl + end for; +end configuration; + +configuration cfg of work.ent is + for rtl + end for; +end configuration; +", + ); + let (library, diagnostics) = new_library_with_diagnostics(&code, "libname"); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("cfg", 2), + "A primary unit has already been declared with name 'cfg' in library 'libname'", + ErrorCode::Duplicate, + ) + .related(code.s1("cfg"), "Previously defined here")], + ); + assert_eq!(library.units.len(), 2); + assert_eq!(library.duplicates.len(), 1); + } + + #[test] + pub fn rejects_illegal_raw_id() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region("signal foo : natural;"); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + let (_, ent) = root + .item_at_cursor(code.source(), code.s1("foo").start()) + .unwrap(); + assert_eq!(root.entity_id_from_raw(ent.id.to_raw()), Some(ent.id)); + assert_eq!(root.entity_id_from_raw(0xFFFF << 32), None); + } +} diff --git a/vhdl_lang/src/analysis/scope.rs b/vhdl_lang/src/analysis/scope.rs new file mode 100644 index 0000000..3b6fbf8 --- /dev/null +++ b/vhdl_lang/src/analysis/scope.rs @@ -0,0 +1,405 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use crate::ast::*; +use crate::data::*; +use crate::named_entity::*; + +use crate::data::error_codes::ErrorCode; +use crate::TokenSpan; +use fnv::FnvHashMap; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::rc::Rc; +use vhdl_lang::TokenAccess; + +#[derive(Default, Clone)] +pub(crate) struct Scope<'a>(Rc>>); + +#[derive(Default)] +struct ScopeInner<'a> { + parent: Option>, + region: Region<'a>, + cache: FnvHashMap>, + anon_idx: usize, +} + +#[derive(Debug)] +pub(crate) enum UndeclaredKind { + Identifier(Symbol), + Operator(Operator), + Character(u8), + Anonymous, +} + +#[derive(Debug)] +pub(crate) enum LookupError { + IntoUnambiguousError(IntoUnambiguousError), + Undeclared(UndeclaredKind), +} + +impl From for LookupError { + fn from(value: IntoUnambiguousError) -> Self { + Self::IntoUnambiguousError(value) + } +} + +impl LookupError { + pub fn into_diagnostic(self, ctx: &dyn TokenAccess, span: impl Into) -> Diagnostic { + let span = span.into(); + match self { + LookupError::IntoUnambiguousError(err) => err.into_diagnostic(ctx, span), + LookupError::Undeclared(kind) => { + let msg = match kind { + UndeclaredKind::Identifier(ident) => format!("No declaration of '{ident}'"), + UndeclaredKind::Operator(operator) => { + format!("No declaration of operator '{operator}'") + } + UndeclaredKind::Character(chr) => format!("No declaration of '{chr}'"), + UndeclaredKind::Anonymous => "No declaration of ".to_owned(), + }; + Diagnostic::new(span.pos(ctx), msg, ErrorCode::Unresolved) + } + } + } +} + +impl<'a> ScopeInner<'a> { + pub fn into_region(self) -> Region<'a> { + self.region + } + + pub fn into_visibility(self) -> Visibility<'a> { + self.region.visibility + } + + pub fn close(&self, diagnostics: &mut dyn DiagnosticHandler) { + self.region.close(diagnostics) + } + + pub fn add(&mut self, ent: EntRef<'a>, diagnostics: &mut dyn DiagnosticHandler) { + self.cache.remove(&ent.designator); + self.region.add(ent, diagnostics) + } + + fn make_potentially_visible( + &mut self, + visible_pos: Option<&SrcPos>, + designator: Designator, + ent: EntRef<'a>, + ) { + self.cache.remove(&ent.designator); + self.region + .visibility + .make_potentially_visible_with_name(visible_pos, designator, ent); + } + + pub fn make_all_potentially_visible( + &mut self, + visible_pos: Option<&SrcPos>, + region: &'a Region<'a>, + ) { + self.cache.clear(); + self.region + .visibility + .make_all_potentially_visible(visible_pos, region); + } + + /// Used when using context clauses + pub fn add_context_visibility(&mut self, visible_pos: Option<&SrcPos>, region: &Region<'a>) { + self.cache.clear(); + // ignores parent but used only for contexts where this is true + self.region + .visibility + .add_context_visibility(visible_pos, ®ion.visibility); + } + + pub fn lookup_immediate(&self, designator: &Designator) -> Option<&NamedEntities<'a>> { + self.region.lookup_immediate(designator) + } + + /// Lookup a named entity declared in this region or an enclosing region + fn lookup_enclosing(&self, designator: &Designator) -> Option> { + // We do not need to look in the enclosing region of the extended region + // since extended region always has the same parent except for protected types + // split into package / package body. + // In that case the package / package body parent of the protected type / body + // is the same extended region anyway + + match self.lookup_immediate(designator).cloned() { + // A non-overloaded name is found in the immediate region + // no need to look further up + Some(NamedEntities::Single(single)) => Some(NamedEntities::Single(single)), + + // The name is overloaded we must also check enclosing regions + Some(NamedEntities::Overloaded(immediate)) => { + if let Some(NamedEntities::Overloaded(enclosing)) = self + .parent + .as_ref() + .and_then(|region| region.0.borrow().lookup_enclosing(designator)) + { + Some(NamedEntities::Overloaded(immediate.with_visible(enclosing))) + } else { + Some(NamedEntities::Overloaded(immediate)) + } + } + None => self + .parent + .as_ref() + .and_then(|region| region.0.borrow().lookup_enclosing(designator)), + } + } + + fn lookup_visiblity_into(&self, designator: &Designator, visible: &mut Visible<'a>) { + self.region.visibility.lookup_into(designator, visible); + if let Some(ref parent) = self.parent { + parent.0.borrow().lookup_visiblity_into(designator, visible); + } + } + + /// Lookup a named entity that was made potentially visible via a use clause + fn lookup_visible( + &self, + designator: &Designator, + ) -> Result>, LookupError> { + let mut visible = Visible::default(); + self.lookup_visiblity_into(designator, &mut visible); + visible + .into_unambiguous(designator) + .map_err(|err| err.into()) + } + + /// Lookup a designator from within the region itself + /// Thus all parent regions and visibility is relevant + fn lookup_uncached(&self, designator: &Designator) -> Result, LookupError> { + let result = if let Some(enclosing) = self.lookup_enclosing(designator) { + match enclosing { + // non overloaded in enclosing region ignores any visible overloaded names + NamedEntities::Single(..) => Some(enclosing), + // In case of overloaded local, non-conflicting visible names are still relevant + NamedEntities::Overloaded(enclosing_overloaded) => { + if let Ok(Some(NamedEntities::Overloaded(overloaded))) = + self.lookup_visible(designator) + { + Some(NamedEntities::Overloaded( + enclosing_overloaded.with_visible(overloaded), + )) + } else { + Some(NamedEntities::Overloaded(enclosing_overloaded)) + } + } + } + } else { + self.lookup_visible(designator)? + }; + + match result { + Some(visible) => Ok(visible), + None => Err(LookupError::Undeclared(match designator { + Designator::Identifier(ident) => UndeclaredKind::Identifier(ident.clone()), + Designator::OperatorSymbol(operator) => UndeclaredKind::Operator(*operator), + Designator::Character(chr) => UndeclaredKind::Character(*chr), + Designator::Anonymous(_) => UndeclaredKind::Anonymous, + })), + } + } + + fn lookup(&mut self, designator: &Designator) -> Result, LookupError> { + if let Some(res) = self.cache.get(designator) { + return Ok(res.clone()); + } + + let ents = self.lookup_uncached(designator)?; + if let Entry::Vacant(vacant) = self.cache.entry(designator.clone()) { + Ok(vacant.insert(ents).clone()) + } else { + unreachable!("Cache miss cannot be followed by occupied entry") + } + } +} + +impl<'a> Scope<'a> { + pub fn new(region: Region<'a>) -> Scope<'a> { + Self(Rc::new(RefCell::new(ScopeInner { + parent: None, + region, + cache: Default::default(), + anon_idx: 0, + }))) + } + + pub fn nested(&self) -> Scope<'a> { + Self(Rc::new(RefCell::new(ScopeInner { + region: Region::default(), + parent: Some(self.clone()), + cache: self.0.borrow().cache.clone(), + anon_idx: 0, + }))) + } + + pub fn with_parent(self, scope: &Scope<'a>) -> Scope<'a> { + Self(Rc::new(RefCell::new(ScopeInner { + parent: Some(scope.clone()), + region: self.into_inner().region, + cache: Default::default(), + anon_idx: 0, + }))) + } + + pub fn extend(region: &Region<'a>, parent: Option<&Scope<'a>>) -> Scope<'a> { + let kind = match region.kind { + RegionKind::PackageDeclaration => RegionKind::PackageBody, + _ => RegionKind::Other, + }; + + let extended_region = Region { + visibility: region.visibility.clone(), + entities: region.entities.clone(), + kind, + }; + + if let Some(parent) = parent { + Scope::new(extended_region).with_parent(parent) + } else { + Scope::new(extended_region) + } + } + + pub fn in_package_declaration(self) -> Scope<'a> { + let inner = self.into_inner(); + + Self(Rc::new(RefCell::new(ScopeInner { + parent: inner.parent, + region: inner.region.in_package_declaration(), + cache: inner.cache, + anon_idx: inner.anon_idx, + }))) + } + + pub fn add(&self, ent: EntRef<'a>, diagnostics: &mut dyn DiagnosticHandler) { + self.0.as_ref().borrow_mut().add(ent, diagnostics); + } + + pub fn make_potentially_visible(&self, visible_pos: Option<&SrcPos>, ent: EntRef<'a>) { + self.0.as_ref().borrow_mut().make_potentially_visible( + visible_pos, + ent.designator().clone(), + ent, + ); + } + + pub fn make_potentially_visible_with_name( + &self, + visible_pos: Option<&SrcPos>, + designator: Designator, + ent: EntRef<'a>, + ) { + self.0 + .as_ref() + .borrow_mut() + .make_potentially_visible(visible_pos, designator, ent); + } + + pub fn make_all_potentially_visible( + &self, + visible_pos: Option<&SrcPos>, + region: &'a Region<'a>, + ) { + self.0 + .as_ref() + .borrow_mut() + .make_all_potentially_visible(visible_pos, region); + } + + pub fn close(&self, diagnostics: &mut dyn DiagnosticHandler) { + self.0.as_ref().borrow().close(diagnostics) + } + + fn into_inner(self) -> ScopeInner<'a> { + if let Ok(cell) = Rc::try_unwrap(self.0) { + cell.into_inner() + } else { + panic!("Expect no child regions"); + } + } + + pub fn into_region(self) -> Region<'a> { + self.into_inner().into_region() + } + + pub fn into_visibility(self) -> Visibility<'a> { + self.into_inner().into_visibility() + } + + pub fn lookup_immediate(&self, designator: &Designator) -> Option> { + let inner = self.0.as_ref().borrow(); + let names = inner.lookup_immediate(designator)?; + + Some(names.clone()) + } + + pub fn lookup(&self, designator: &Designator) -> Result, LookupError> { + self.0.as_ref().borrow_mut().lookup(designator) + } + + /// Used when using context clauses + pub fn add_context_visibility(&self, visible_pos: Option<&SrcPos>, region: &Region<'a>) { + self.0 + .as_ref() + .borrow_mut() + .add_context_visibility(visible_pos, region) + } + + pub fn next_anonymous(&self) -> usize { + let mut inner = self.0.borrow_mut(); + let idx = inner.anon_idx; + inner.anon_idx += 1; + idx + } + + pub fn anonymous_designator(&self) -> Designator { + Designator::Anonymous(self.next_anonymous()) + } +} + +impl<'a> NamedEntities<'a> { + pub(crate) fn make_potentially_visible_in( + &self, + visible_pos: Option<&SrcPos>, + scope: &Scope<'a>, + ) { + match self { + Self::Single(ent) => { + scope.make_potentially_visible(visible_pos, ent); + } + Self::Overloaded(overloaded) => { + for ent in overloaded.entities() { + scope.make_potentially_visible(visible_pos, ent.into()); + } + } + } + } +} + +impl Diagnostic { + pub(crate) fn duplicate_error( + name: &impl std::fmt::Display, + pos: &SrcPos, + prev_pos: Option<&SrcPos>, + ) -> Diagnostic { + let mut diagnostic = Diagnostic::new( + pos, + format!("Duplicate declaration of '{name}'"), + ErrorCode::Duplicate, + ); + + if let Some(prev_pos) = prev_pos { + diagnostic.add_related(prev_pos, "Previously defined here"); + } + + diagnostic + } +} diff --git a/vhdl_lang/src/analysis/semantic.rs b/vhdl_lang/src/analysis/semantic.rs new file mode 100644 index 0000000..4407df0 --- /dev/null +++ b/vhdl_lang/src/analysis/semantic.rs @@ -0,0 +1,253 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use super::analyze::*; +use super::names::ResolvedName; +use super::overloaded::Disambiguated; +use super::overloaded::SubprogramKind; +use super::scope::*; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn choice_with_ttyp( + &self, + scope: &Scope<'a>, + ttyp: Option>, + choices: &mut [WithTokenSpan], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for choice in choices.iter_mut() { + match choice.item { + Choice::Expression(ref mut expr) => { + if let Some(ttyp) = ttyp { + self.expr_pos_with_ttyp(scope, ttyp, choice.span, expr, diagnostics)?; + } else { + self.expr_pos_unknown_ttyp(scope, choice.span, expr, diagnostics)?; + } + } + Choice::DiscreteRange(ref mut drange) => { + if let Some(ttyp) = ttyp { + self.drange_with_ttyp(scope, ttyp, drange, diagnostics)?; + } else { + self.drange_unknown_type(scope, drange, diagnostics)?; + } + } + Choice::Others => {} + } + } + Ok(()) + } + + pub fn analyze_assoc_elems( + &self, + scope: &Scope<'a>, + elems: &mut [AssociationElement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for AssociationElement { actual, .. } in elems.iter_mut() { + match actual.item { + ActualPart::Expression(ref mut expr) => { + self.expr_pos_unknown_ttyp(scope, actual.span, expr, diagnostics)?; + } + ActualPart::Open => {} + } + } + Ok(()) + } + + pub fn analyze_procedure_call( + &self, + scope: &Scope<'a>, + fcall: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let fcall_span = fcall.span; + let CallOrIndexed { name, parameters } = &mut fcall.item; + + let resolved = + match as_fatal(self.name_resolve(scope, name.span, &mut name.item, diagnostics))? { + Some(resolved) => resolved, + None => { + // Continue checking missing names even if procedure is not found + self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?; + return Ok(()); + } + }; + + match resolved { + ResolvedName::Overloaded(ref des, names) => { + match as_fatal(self.disambiguate( + scope, + &fcall_span.pos(self.ctx), + des, + &mut parameters.items, + SubprogramKind::Procedure, + names.entities().collect(), + diagnostics, + ))? { + Some(Disambiguated::Ambiguous(candidates)) => { + diagnostics.push(Diagnostic::ambiguous_call(self.ctx, des, candidates)) + } + Some(Disambiguated::Unambiguous(ent)) => { + name.set_unique_reference(&ent); + + if !ent.is_procedure() { + let mut diagnostic = Diagnostic::new( + name.pos(self.ctx), + "Invalid procedure call", + ErrorCode::InvalidCall, + ); + for ent in names.sorted_entities() { + if let Some(decl_pos) = ent.decl_pos() { + diagnostic.add_related( + decl_pos, + format!("{} is not a procedure", ent.describe()), + ); + } + } + diagnostics.push(diagnostic); + } else if ent.is_uninst_subprogram_body() { + diagnostics.add( + name.pos(self.ctx), + format!("uninstantiated {} cannot be called", ent.describe()), + ErrorCode::InvalidCall, + ) + } + } + None => {} + } + } + ResolvedName::Final(ent) => { + if let AnyEntKind::Component(region) = ent.kind() { + name.set_unique_reference(ent); + let (generic_region, port_region) = region.to_entity_formal(); + self.check_association( + &fcall.item.name.pos(self.ctx), + &generic_region, + scope, + &mut [], + diagnostics, + )?; + self.check_association( + &fcall.item.name.pos(self.ctx), + &port_region, + scope, + &mut [], + diagnostics, + )?; + } else { + diagnostics.add( + name.pos(self.ctx), + format!("{} is not a procedure", resolved.describe_type()), + ErrorCode::MismatchedKinds, + ); + self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?; + } + } + resolved => { + diagnostics.add( + name.pos(self.ctx), + format!("{} is not a procedure", resolved.describe_type()), + ErrorCode::MismatchedKinds, + ); + self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?; + } + }; + + Ok(()) + } +} + +impl Diagnostic { + pub fn add_subprogram_candidates<'a>( + &mut self, + prefix: &str, + candidates: impl IntoIterator>, + ) { + let mut candidates: Vec<_> = candidates.into_iter().collect(); + candidates.sort_by(|x, y| x.decl_pos().cmp(&y.decl_pos())); + + for ent in candidates { + if let Some(decl_pos) = ent.decl_pos() { + self.add_related(decl_pos, format!("{} {}", prefix, ent.describe())) + } + } + } + + pub fn add_type_candididates<'a>( + &mut self, + prefix: &str, + candidates: impl IntoIterator>, + ) { + let mut candidates: Vec<_> = candidates.into_iter().collect(); + candidates.sort_by(|x, y| x.decl_pos().cmp(&y.decl_pos())); + + for ent in candidates { + if let Some(decl_pos) = ent.decl_pos() { + self.add_related(decl_pos, format!("{} {}", prefix, ent.describe())) + } + } + } +} + +impl<'a> ResolvedName<'a> { + pub(super) fn kind_error(&self, pos: impl AsRef, expected: &str) -> Diagnostic { + let mut error = Diagnostic::mismatched_kinds( + pos, + format!("Expected {expected}, got {}", self.describe()), + ); + if let Some(decl_pos) = self.decl_pos() { + error.add_related(decl_pos, "Defined here"); + } + error + } +} + +impl Diagnostic { + pub(crate) fn type_mismatch( + pos: &SrcPos, + desc: &str, + expected_type: TypeEnt<'_>, + ) -> Diagnostic { + Diagnostic::new( + pos, + format!("{} does not match {}", desc, expected_type.describe(),), + ErrorCode::TypeMismatch, + ) + } + + pub(crate) fn invalid_selected_name_prefix( + named_entity: EntRef<'_>, + prefix: &SrcPos, + ) -> Diagnostic { + Diagnostic::mismatched_kinds( + prefix, + capitalize(&format!( + "{} may not be the prefix of a selected name", + named_entity.describe(), + )), + ) + } + + pub(crate) fn no_declaration_within( + named_entity: EntRef<'_>, + pos: &SrcPos, + suffix: &Designator, + ) -> Diagnostic { + Diagnostic::new( + pos, + format!( + "No declaration of '{}' within {}", + suffix, + named_entity.describe(), + ), + ErrorCode::Unresolved, + ) + } +} diff --git a/vhdl_lang/src/analysis/sequential.rs b/vhdl_lang/src/analysis/sequential.rs new file mode 100644 index 0000000..795c436 --- /dev/null +++ b/vhdl_lang/src/analysis/sequential.rs @@ -0,0 +1,445 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use crate::HasTokenSpan; +use analyze::*; +use target::AssignmentType; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn define_labels_for_sequential_part( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + statements: &mut [LabeledSequentialStatement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for statement in statements.iter_mut() { + let span = statement.span(); + let parent = if let Some(ref mut label) = statement.label.tree { + let ent = self.arena.explicit( + label.name(), + parent, + AnyEntKind::Sequential(statement.statement.item.label_typ()), + Some(label.pos(self.ctx)), + span, + Some(self.source()), + ); + statement.label.decl.set(ent.id()); + scope.add(ent, diagnostics); + ent + } else if statement.statement.item.can_have_label() { + // Generate an anonymous label if it is not explicitly defined + let ent = self.arena.alloc( + scope.anonymous_designator(), + Some(parent), + Related::None, + AnyEntKind::Sequential(statement.statement.item.label_typ()), + None, + span, + Some(self.source()), + ); + statement.label.decl.set(ent.id()); + ent + } else { + parent + }; + + match statement.statement.item { + SequentialStatement::If(ref mut ifstmt) => { + let Conditionals { + conditionals, + else_item, + } = &mut ifstmt.conds; + + for conditional in conditionals { + self.define_labels_for_sequential_part( + scope, + parent, + &mut conditional.item, + diagnostics, + )?; + } + if let Some((else_item, _)) = else_item { + self.define_labels_for_sequential_part( + scope, + parent, + else_item, + diagnostics, + )?; + } + } + + SequentialStatement::Case(ref mut case_stmt) => { + for alternative in case_stmt.alternatives.iter_mut() { + self.define_labels_for_sequential_part( + scope, + parent, + &mut alternative.item, + diagnostics, + )?; + } + } + SequentialStatement::Loop(ref mut loop_stmt) => { + self.define_labels_for_sequential_part( + scope, + parent, + &mut loop_stmt.statements, + diagnostics, + )?; + } + _ => { + // Does not have sequential part + } + } + } + + Ok(()) + } + + fn analyze_sequential_statement( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + statement: &mut LabeledSequentialStatement, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let statement_span = statement.statement.span; + match statement.statement.item { + SequentialStatement::Return(ref mut ret) => { + let ReturnStatement { ref mut expression } = ret; + + match SequentialRoot::from(parent) { + SequentialRoot::Function(ttyp) => { + if let Some(ref mut expression) = expression { + self.expr_with_ttyp(scope, ttyp, expression, diagnostics)?; + } else { + diagnostics.add( + statement.statement.pos(self.ctx), + "Functions cannot return without a value", + ErrorCode::VoidReturn, + ); + } + } + SequentialRoot::Procedure => { + if expression.is_some() { + diagnostics.add( + statement.statement.pos(self.ctx), + "Procedures cannot return a value", + ErrorCode::NonVoidReturn, + ); + } + } + SequentialRoot::Process => { + diagnostics.add( + statement.statement.pos(self.ctx), + "Cannot return from a process", + ErrorCode::IllegalReturn, + ); + } + } + } + SequentialStatement::Wait(ref mut wait_stmt) => { + let WaitStatement { + sensitivity_clause, + condition_clause, + timeout_clause, + } = wait_stmt; + if let Some(list) = sensitivity_clause { + self.sensitivity_list_check(scope, list, diagnostics)?; + } + if let Some(expr) = condition_clause { + self.boolean_expr(scope, expr, diagnostics)?; + } + if let Some(expr) = timeout_clause { + self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; + } + } + SequentialStatement::Assert(ref mut assert_stmt) => { + let AssertStatement { + condition, + report, + severity, + } = assert_stmt; + self.boolean_expr(scope, condition, diagnostics)?; + if let Some(expr) = report { + self.expr_with_ttyp(scope, self.string(), expr, diagnostics)?; + } + if let Some(expr) = severity { + self.expr_with_ttyp(scope, self.severity_level(), expr, diagnostics)?; + } + } + SequentialStatement::Report(ref mut report_stmt) => { + let ReportStatement { report, severity } = report_stmt; + self.expr_with_ttyp(scope, self.string(), report, diagnostics)?; + if let Some(expr) = severity { + self.expr_with_ttyp(scope, self.severity_level(), expr, diagnostics)?; + } + } + SequentialStatement::Exit(ref mut exit_stmt) => { + let ExitStatement { + condition, + loop_label, + } = exit_stmt; + + if let Some(loop_label) = loop_label { + self.check_loop_label(scope, parent, loop_label, diagnostics); + } else if !find_outer_loop(parent, None) { + diagnostics.add( + statement_span.pos(self.ctx), + "Exit can only be used inside a loop", + ErrorCode::ExitOutsideLoop, + ) + } + + if let Some(expr) = condition { + self.boolean_expr(scope, expr, diagnostics)?; + } + } + SequentialStatement::Next(ref mut next_stmt) => { + let NextStatement { + condition, + loop_label, + } = next_stmt; + + if let Some(loop_label) = loop_label { + self.check_loop_label(scope, parent, loop_label, diagnostics); + } else if !find_outer_loop(parent, None) { + diagnostics.add( + statement_span.pos(self.ctx), + "Next can only be used inside a loop", + ErrorCode::NextOutsideLoop, + ) + } + + if let Some(expr) = condition { + self.boolean_expr(scope, expr, diagnostics)?; + } + } + SequentialStatement::If(ref mut ifstmt) => { + let Conditionals { + conditionals, + else_item, + } = &mut ifstmt.conds; + + // @TODO write generic function for this + for conditional in conditionals { + let Conditional { condition, item } = conditional; + self.boolean_expr(scope, condition, diagnostics)?; + self.analyze_sequential_part(scope, parent, item, diagnostics)?; + } + if let Some((else_item, _)) = else_item { + self.analyze_sequential_part(scope, parent, else_item, diagnostics)?; + } + } + SequentialStatement::Case(ref mut case_stmt) => { + let CaseStatement { + expression, + alternatives, + .. + } = case_stmt; + let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?; + for alternative in alternatives.iter_mut() { + let Alternative { + choices, + item, + span: _, + } = alternative; + self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?; + self.analyze_sequential_part(scope, parent, item, diagnostics)?; + } + } + SequentialStatement::Loop(ref mut loop_stmt) => { + let LoopStatement { + iteration_scheme, + statements, + .. + } = loop_stmt; + match iteration_scheme { + Some(IterationScheme::For(ref mut index, ref mut drange)) => { + let typ = as_fatal(self.drange_type(scope, drange, diagnostics))?; + let region = scope.nested(); + region.add( + self.define( + index, + parent, + AnyEntKind::LoopParameter(typ), + index.tree.token.into(), + ), + diagnostics, + ); + self.analyze_sequential_part(®ion, parent, statements, diagnostics)?; + } + Some(IterationScheme::While(ref mut expr)) => { + self.boolean_expr(scope, expr, diagnostics)?; + self.analyze_sequential_part(scope, parent, statements, diagnostics)?; + } + None => { + self.analyze_sequential_part(scope, parent, statements, diagnostics)?; + } + } + } + SequentialStatement::ProcedureCall(ref mut pcall) => { + self.analyze_procedure_call(scope, pcall, diagnostics)?; + } + SequentialStatement::SignalAssignment(ref mut assign) => { + self.analyze_waveform_assignment(scope, assign, diagnostics)?; + } + SequentialStatement::VariableAssignment(ref mut assign) => { + let VariableAssignment { target, rhs } = assign; + self.analyze_expr_assignment( + scope, + target, + AssignmentType::Variable, + rhs, + diagnostics, + )?; + } + SequentialStatement::SignalForceAssignment(ref mut assign) => { + let SignalForceAssignment { + target, + force_mode: _, + rhs, + } = assign; + self.analyze_expr_assignment( + scope, + target, + AssignmentType::Signal, + rhs, + diagnostics, + )?; + } + SequentialStatement::SignalReleaseAssignment(ref mut assign) => { + let SignalReleaseAssignment { + target, + force_mode: _, + span: _, + } = assign; + as_fatal(self.resolve_target(scope, target, AssignmentType::Signal, diagnostics))?; + } + SequentialStatement::Null => {} + } + Ok(()) + } + + fn check_loop_label( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + label: &mut WithRef, + diagnostics: &mut dyn DiagnosticHandler, + ) { + match scope.lookup(&Designator::Identifier(label.item.item.clone())) { + Ok(NamedEntities::Single(ent)) => { + label.set_unique_reference(ent); + if matches!(ent.kind(), AnyEntKind::Sequential(Some(Sequential::Loop))) { + if !find_outer_loop(parent, Some(label.item.name())) { + diagnostics.add( + label.item.pos(self.ctx), + format!("Cannot be used outside of loop '{}'", ent.designator()), + ErrorCode::InvalidLoopLabel, + ); + } + } else { + diagnostics.add( + label.item.pos(self.ctx), + format!("Expected loop label, got {}", ent.describe()), + ErrorCode::MismatchedKinds, + ); + } + } + Ok(NamedEntities::Overloaded(_)) => diagnostics.add( + label.item.pos(self.ctx), + format!( + "Expected loop label, got overloaded name {}", + &label.item.item + ), + ErrorCode::MismatchedKinds, + ), + Err(diag) => { + diagnostics.push(diag.into_diagnostic(self.ctx, label.item.token)); + } + } + } + + pub fn analyze_sequential_part( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + statements: &mut [LabeledSequentialStatement], + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + for statement in statements.iter_mut() { + let parent = if let Some(id) = statement.label.decl.get() { + self.arena.get(id) + } else { + parent + }; + + self.analyze_sequential_statement(scope, parent, statement, diagnostics)?; + } + + Ok(()) + } +} + +enum SequentialRoot<'a> { + Process, + Procedure, + Function(TypeEnt<'a>), +} + +fn find_outer_loop(ent: EntRef<'_>, label: Option<&Symbol>) -> bool { + match ent.kind() { + AnyEntKind::Sequential(Some(Sequential::Loop)) => { + if let Some(label) = label { + if matches!(ent.designator(), Designator::Identifier(ident) if ident == label) { + return true; + } + } else { + return true; + } + } + AnyEntKind::Sequential(_) => {} + _ => { + return false; + } + } + + if let Some(parent) = ent.parent { + find_outer_loop(parent, label) + } else { + false + } +} + +impl<'a> From> for SequentialRoot<'a> { + fn from(value: EntRef<'a>) -> Self { + match value.kind() { + AnyEntKind::Overloaded(overloaded) => { + if let Some(return_type) = overloaded.signature().return_type() { + SequentialRoot::Function(return_type) + } else { + SequentialRoot::Procedure + } + } + AnyEntKind::Sequential(_) => { + if let Some(parent) = value.parent { + SequentialRoot::from(parent) + } else { + // A sequential statement must always have a parent this should never happen + SequentialRoot::Process + } + } + AnyEntKind::Concurrent(Some(Concurrent::Process)) => SequentialRoot::Process, + _ => SequentialRoot::Process, + } + } +} diff --git a/vhdl_lang/src/analysis/standard.rs b/vhdl_lang/src/analysis/standard.rs new file mode 100644 index 0000000..b06fae4 --- /dev/null +++ b/vhdl_lang/src/analysis/standard.rs @@ -0,0 +1,1140 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com + +use crate::ast::Declaration; +use crate::ast::Designator; +use crate::ast::Mode; +use crate::ast::ObjectClass; +use crate::ast::Operator; +use crate::data::DiagnosticHandler; +use crate::syntax::Symbols; +use crate::HasTokenSpan; +use vhdl_lang::ast::token_range::WithTokenSpan; +use vhdl_lang::TokenAccess; + +use super::analyze::AnalyzeContext; +use crate::named_entity::*; + +#[derive(Clone, Copy)] +pub(crate) struct UniversalTypes { + pub integer: EntityId, + pub real: EntityId, +} + +impl UniversalTypes { + pub fn new<'a>(arena: &'a Arena, standard_pkg: EntRef<'a>, symbols: &Symbols) -> Self { + let integer = arena.explicit( + Designator::Identifier(symbols.symtab().insert_utf8("universal_integer")), + standard_pkg, + AnyEntKind::Type(Type::Universal(UniversalType::Integer)), + standard_pkg.decl_pos(), + standard_pkg.src_span, + standard_pkg.source.clone(), + ); + + let real = arena.explicit( + Designator::Identifier(symbols.symtab().insert_utf8("universal_real")), + standard_pkg, + AnyEntKind::Type(Type::Universal(UniversalType::Real)), + standard_pkg.decl_pos(), + standard_pkg.src_span, + standard_pkg.source.clone(), + ); + + Self { + real: real.id(), + integer: integer.id(), + } + } +} + +pub(crate) struct StandardTypes { + pub boolean: EntityId, + pub boolean_vector: EntityId, + pub bit: EntityId, + pub bit_vector: EntityId, + pub character: EntityId, + pub string: EntityId, + pub integer: EntityId, + pub natural: EntityId, + pub real: EntityId, + pub time: EntityId, + pub file_open_kind: EntityId, + pub file_open_status: EntityId, + pub severity_level: EntityId, +} + +impl StandardTypes { + pub fn new<'a>( + ctx: &dyn TokenAccess, + arena: &'a Arena, + standard_pkg: EntRef<'a>, + decls: &mut [WithTokenSpan], + ) -> Self { + let mut boolean = None; + let mut boolean_vector = None; + let mut bit = None; + let mut bit_vector = None; + let mut character = None; + let mut string = None; + let mut integer = None; + let mut natural = None; + let mut real = None; + let mut time = None; + let mut file_open_status = None; + let mut file_open_kind = None; + let mut severity_level = None; + + // Reserve space in the arena for the standard types + for decl in decls.iter_mut() { + if let Declaration::Type(ref mut type_decl) = decl.item { + let id = arena + .alloc( + Designator::Identifier(type_decl.ident.tree.item.clone()), + Some(standard_pkg), + Related::None, + AnyEntKind::Type(Type::Incomplete), + Some(type_decl.ident.pos(ctx).clone()), + type_decl.span(), + standard_pkg.source.clone(), + ) + .id(); + let name = type_decl.ident.tree.item.name(); + match name.bytes.as_slice() { + b"BOOLEAN" => { + boolean = Some(id); + } + b"BOOLEAN_VECTOR" => { + boolean_vector = Some(id); + } + b"BIT" => { + bit = Some(id); + } + b"BIT_VECTOR" => { + bit_vector = Some(id); + } + b"CHARACTER" => { + character = Some(id); + } + b"STRING" => { + string = Some(id); + } + b"INTEGER" => { + integer = Some(id); + } + b"NATURAL" => { + natural = Some(id); + } + b"REAL" => { + real = Some(id); + } + b"TIME" => { + time = Some(id); + } + b"FILE_OPEN_KIND" => { + file_open_kind = Some(id); + } + b"FILE_OPEN_STATUS" => { + file_open_status = Some(id); + } + b"SEVERITY_LEVEL" => { + severity_level = Some(id); + } + _ => { + continue; + } + } + type_decl.ident.decl.set(id); + } + } + + Self { + boolean: boolean.unwrap(), + boolean_vector: boolean_vector.unwrap(), + bit: bit.unwrap(), + bit_vector: bit_vector.unwrap(), + character: character.unwrap(), + string: string.unwrap(), + integer: integer.unwrap(), + natural: natural.unwrap(), + real: real.unwrap(), + time: time.unwrap(), + file_open_kind: file_open_kind.unwrap(), + file_open_status: file_open_status.unwrap(), + severity_level: severity_level.unwrap(), + } + } +} + +impl<'a, 't> AnalyzeContext<'a, 't> { + fn ident(&self, name: &str) -> Designator { + Designator::Identifier(self.root.symbol_utf8(name)) + } + + fn standard_types(&self) -> &StandardTypes { + self.root.standard_types.as_ref().unwrap() + } + + pub(crate) fn string(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().string) + } + + pub(crate) fn boolean(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().boolean) + } + + pub(crate) fn boolean_vector(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().boolean_vector) + } + + pub(crate) fn bit(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().bit) + } + + pub(crate) fn bit_vector(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().bit_vector) + } + + pub(crate) fn natural(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().natural) + } + + #[allow(dead_code)] + pub(crate) fn character(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().character) + } + + pub(crate) fn universal_integer(&self) -> BaseType<'a> { + self.arena + .get_type(self.root.universal.as_ref().unwrap().integer) + .base() + } + + pub(crate) fn universal_real(&self) -> BaseType<'a> { + self.arena + .get_type(self.root.universal.as_ref().unwrap().real) + .base() + } + + pub(crate) fn integer(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().integer) + } + + pub(crate) fn real(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().real) + } + + pub(crate) fn time(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().time) + } + + fn file_open_kind(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().file_open_kind) + } + + fn file_open_status(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().file_open_status) + } + + pub(crate) fn severity_level(&self) -> TypeEnt<'a> { + self.arena.get_type(self.standard_types().severity_level) + } + + /// Create implicit MAXIMUM/MINIMUM + // function MINIMUM (L, R: T) return T; + // function MAXIMUM (L, R: T) return T; + fn min_or_maximum(&self, name: &str, type_ent: TypeEnt<'a>) -> EntRef<'a> { + self.implicit_subpgm( + type_ent, + self.ident(name), + [ + ( + self.ident("L"), + AnyEntKind::Object(Object::const_param(Subtype::new(type_ent))), + ), + ( + self.ident("R"), + AnyEntKind::Object(Object::const_param(Subtype::new(type_ent))), + ), + ], + Some(type_ent), + ) + .into() + } + + fn elementwise_min_or_maximum( + &self, + name: &str, + arr_typ: TypeEnt<'a>, + elem_typ: TypeEnt<'a>, + ) -> EntRef<'a> { + self.implicit_subpgm( + arr_typ, + self.ident(name), + [( + self.ident("L"), + AnyEntKind::Object(Object::const_param(Subtype::new(arr_typ))), + )], + Some(elem_typ), + ) + .into() + } + + fn unary(&self, op: Operator, typ: TypeEnt<'a>, return_type: TypeEnt<'a>) -> EntRef<'a> { + self.implicit_subpgm( + typ, + Designator::OperatorSymbol(op), + [( + Designator::Anonymous(0), + AnyEntKind::Object(Object::const_param(Subtype::new(typ))), + )], + Some(return_type), + ) + .into() + } + + pub(crate) fn symmetric_unary(&self, op: Operator, typ: TypeEnt<'a>) -> EntRef<'a> { + self.unary(op, typ, typ) + } + + fn implicit_subpgm( + &self, + implicit_of: TypeEnt<'a>, + des: Designator, + formals: impl IntoIterator)>, + return_type: Option>, + ) -> OverloadedEnt<'a> { + let mut region = FormalRegion::new_params(); + + let subpgm_ent = self.arena.implicit( + implicit_of.into(), + des, + AnyEntKind::Overloaded(Overloaded::SubprogramDecl(Signature::new( + FormalRegion::new_params(), + return_type, + ))), + ); + + for (name, kind) in formals.into_iter() { + region.add(self.arena.explicit( + name, + subpgm_ent, + kind, + implicit_of.decl_pos(), + implicit_of.src_span, + Some(self.source()), + )); + } + + let kind = if let Some(return_type) = return_type { + AnyEntKind::new_function_decl(region, return_type) + } else { + AnyEntKind::new_procedure_decl(region) + }; + + unsafe { + subpgm_ent.set_kind(kind); + } + OverloadedEnt::from_any(subpgm_ent).unwrap() + } + + fn binary( + &self, + op: Operator, + implicit_of: TypeEnt<'a>, + left: TypeEnt<'a>, + right: TypeEnt<'a>, + return_type: TypeEnt<'a>, + ) -> EntRef<'a> { + self.implicit_subpgm( + implicit_of, + Designator::OperatorSymbol(op), + [ + ( + Designator::Anonymous(0), + AnyEntKind::Object(Object::const_param(Subtype::new(left))), + ), + ( + Designator::Anonymous(1), + AnyEntKind::Object(Object::const_param(Subtype::new(right))), + ), + ], + Some(return_type), + ) + .into() + } + + fn symmetric_binary(&self, op: Operator, typ: TypeEnt<'a>) -> EntRef<'a> { + self.binary(op, typ, typ, typ, typ) + } + + pub fn minimum(&self, type_ent: TypeEnt<'a>) -> EntRef<'a> { + self.min_or_maximum("MINIMUM", type_ent) + } + + pub fn maximum(&self, type_ent: TypeEnt<'a>) -> EntRef<'a> { + self.min_or_maximum("MAXIMUM", type_ent) + } + + pub fn create_implicit_file_type_subprograms( + &self, + file_type: TypeEnt<'a>, + type_mark: TypeEnt<'a>, + ) -> Vec> { + let mut implicit = Vec::new(); + + let string = self.string(); + let boolean = self.boolean(); + let file_open_kind = self.file_open_kind(); + let file_open_status = self.file_open_status(); + + // procedure FILE_OPEN (file F: FT; External_Name: in STRING; Open_Kind: in FILE_OPEN_KIND := READ_MODE); + { + let ent = self.implicit_subpgm( + file_type, + self.ident("FILE_OPEN"), + [ + (self.ident("F"), AnyEntKind::InterfaceFile(file_type)), + ( + self.ident("External_Name"), + AnyEntKind::Object(Object::const_param(Subtype::new(string))), + ), + ( + self.ident("Open_Kind"), + AnyEntKind::Object( + Object::const_param(Subtype::new(file_open_kind)).with_default(), + ), + ), + ], + None, + ); + implicit.push(ent.into()); + } + + // procedure FILE_OPEN (Status: out FILE_OPEN_STATUS; file F: FT; External_Name: in STRING; Open_Kind: in FILE_OPEN_KIND := READ_MODE); + { + let ent = self.implicit_subpgm( + file_type, + self.ident("FILE_OPEN"), + [ + ( + self.ident("Status"), + AnyEntKind::Object(Object::const_param(Subtype::new(file_open_status))), + ), + (self.ident("F"), AnyEntKind::InterfaceFile(file_type)), + ( + self.ident("External_Name"), + AnyEntKind::Object(Object::const_param(Subtype::new(string))), + ), + ( + self.ident("Open_Kind"), + AnyEntKind::Object( + Object::const_param(Subtype::new(file_open_kind)).with_default(), + ), + ), + ], + None, + ); + implicit.push(ent.into()); + } + + // procedure FILE_CLOSE (file F: FT); + { + let ent = self.implicit_subpgm( + file_type, + self.ident("FILE_CLOSE"), + [(self.ident("F"), AnyEntKind::InterfaceFile(file_type))], + None, + ); + implicit.push(ent.into()); + } + + // procedure READ (file F: FT; VALUE: out TM); + { + let ent = self.implicit_subpgm( + file_type, + self.ident("READ"), + [ + (self.ident("F"), AnyEntKind::InterfaceFile(file_type)), + ( + self.ident("VALUE"), + AnyEntKind::Object(Object { + class: ObjectClass::Variable, + iface: Some(ObjectInterface::Parameter(InterfaceMode::Simple( + Mode::Out, + ))), + subtype: Subtype::new(type_mark), + has_default: false, + }), + ), + ], + None, + ); + implicit.push(ent.into()); + } + + // procedure WRITE (file F: FT; VALUE: in TM); + { + let ent = self.implicit_subpgm( + file_type, + self.ident("WRITE"), + [ + (self.ident("F"), AnyEntKind::InterfaceFile(file_type)), + ( + self.ident("VALUE"), + AnyEntKind::Object(Object::const_param(Subtype::new(type_mark))), + ), + ], + None, + ); + implicit.push(ent.into()); + } + + // procedure FLUSH (file F: FT); + { + let ent = self.implicit_subpgm( + file_type, + self.ident("FLUSH"), + [(self.ident("F"), AnyEntKind::InterfaceFile(file_type))], + None, + ); + implicit.push(ent.into()); + } + + // function ENDFILE (file F: FT) return BOOLEAN; + { + let ent = self.implicit_subpgm( + file_type, + self.ident("ENDFILE"), + [(self.ident("F"), AnyEntKind::InterfaceFile(file_type))], + Some(boolean), + ); + implicit.push(ent.into()); + } + + implicit + } + + /// Create implicit TO_STRING + /// function TO_STRING (VALUE: T) return STRING; + pub fn create_to_string(&self, type_ent: TypeEnt<'a>) -> EntRef<'a> { + self.to_x_string("TO_STRING", type_ent) + } + + /// Create implicit function returning string + /// function (VALUE: T) return STRING; + pub fn to_x_string(&self, name: &str, type_ent: TypeEnt<'a>) -> EntRef<'a> { + self.implicit_subpgm( + type_ent, + self.ident(name), + [( + self.ident("VALUE"), + AnyEntKind::Object(Object::const_param(Subtype::new(type_ent))), + )], + Some(self.string()), + ) + .into() + } + + /// Create implicit DEALLOCATE + /// procedure DEALLOCATE (P: inout AT); + pub fn deallocate(&self, type_ent: TypeEnt<'a>) -> EntRef<'a> { + self.implicit_subpgm( + type_ent, + self.ident("DEALLOCATE"), + [( + self.ident("P"), + AnyEntKind::Object(Object { + class: ObjectClass::Variable, + iface: Some(ObjectInterface::Parameter(InterfaceMode::Simple( + Mode::InOut, + ))), + subtype: Subtype::new(type_ent.to_owned()), + has_default: false, + }), + )], + None, + ) + .into() + } + + pub fn comparison(&self, op: Operator, typ: TypeEnt<'a>) -> EntRef<'a> { + self.binary(op, typ, typ, typ, self.boolean()) + } + + pub fn comparators(&self, typ: TypeEnt<'a>) -> impl Iterator> { + [ + self.comparison(Operator::EQ, typ), + self.comparison(Operator::NE, typ), + self.comparison(Operator::LT, typ), + self.comparison(Operator::LTE, typ), + self.comparison(Operator::GT, typ), + self.comparison(Operator::GTE, typ), + ] + .into_iter() + } + + pub fn numeric_implicits( + &self, + kind: UniversalType, + typ: TypeEnt<'a>, + ) -> impl Iterator> { + let integer = self.integer(); + + [ + self.minimum(typ), + self.maximum(typ), + self.create_to_string(typ), + self.symmetric_unary(Operator::Minus, typ), + self.symmetric_unary(Operator::Plus, typ), + self.symmetric_binary(Operator::Plus, typ), + self.symmetric_binary(Operator::Minus, typ), + // 9.2.7 Multiplying operators + self.symmetric_binary(Operator::Times, typ), + self.symmetric_binary(Operator::Div, typ), + // 9.2.8 Miscellaneous operators + self.symmetric_unary(Operator::Abs, typ), + self.binary(Operator::Pow, typ, typ, integer, typ), + ] + .into_iter() + .chain( + if kind == UniversalType::Integer { + Some( + [ + self.symmetric_binary(Operator::Mod, typ), + self.symmetric_binary(Operator::Rem, typ), + ] + .into_iter(), + ) + } else { + None + } + .into_iter() + .flatten(), + ) + .chain(self.comparators(typ)) + } + + pub fn universal_implicits( + &self, + kind: UniversalType, + typ: TypeEnt<'a>, + ) -> impl Iterator> { + [ + self.minimum(typ), + self.maximum(typ), + self.create_to_string(typ), + self.symmetric_unary(Operator::Minus, typ), + self.symmetric_unary(Operator::Plus, typ), + self.symmetric_binary(Operator::Plus, typ), + self.symmetric_binary(Operator::Minus, typ), + // 9.2.7 Multiplying operators + self.symmetric_binary(Operator::Times, typ), + self.symmetric_binary(Operator::Div, typ), + // 9.2.8 Miscellaneous operators + self.symmetric_unary(Operator::Abs, typ), + self.binary( + Operator::Pow, + typ, + typ, + self.universal_integer().into(), + typ, + ), + ] + .into_iter() + .chain(match kind { + UniversalType::Integer => itertools::Either::Left( + [ + self.symmetric_binary(Operator::Mod, typ), + self.symmetric_binary(Operator::Rem, typ), + ] + .into_iter(), + ), + UniversalType::Real => { + // Universal real + itertools::Either::Right( + [ + self.binary( + Operator::Times, + typ, + typ, + self.universal_integer().into(), + typ, + ), + self.binary( + Operator::Times, + typ, + self.universal_integer().into(), + typ, + typ, + ), + self.binary( + Operator::Div, + typ, + typ, + self.universal_integer().into(), + typ, + ), + ] + .into_iter(), + ) + } + }) + .chain(self.comparators(typ)) + } + + pub fn physical_implicits(&self, typ: TypeEnt<'a>) -> impl Iterator> { + let integer = self.integer(); + let real = self.real(); + + [ + self.minimum(typ), + self.maximum(typ), + self.symmetric_unary(Operator::Minus, typ), + self.symmetric_unary(Operator::Plus, typ), + self.symmetric_unary(Operator::Abs, typ), + self.symmetric_binary(Operator::Plus, typ), + self.symmetric_binary(Operator::Minus, typ), + // 9.2.7 Multiplying operators + self.binary(Operator::Times, typ, typ, integer, typ), + self.binary(Operator::Times, typ, typ, real, typ), + self.binary(Operator::Times, typ, integer, typ, typ), + self.binary(Operator::Times, typ, real, typ, typ), + self.binary(Operator::Div, typ, typ, integer, typ), + self.binary(Operator::Div, typ, typ, real, typ), + self.binary( + Operator::Div, + typ, + typ, + typ, + self.universal_integer().into(), + ), + self.symmetric_binary(Operator::Mod, typ), + self.symmetric_binary(Operator::Rem, typ), + ] + .into_iter() + .chain(self.comparators(typ)) + } + + pub fn enum_implicits( + &self, + typ: TypeEnt<'a>, + matching_op: bool, + ) -> impl Iterator> { + [ + self.create_to_string(typ), + self.minimum(typ), + self.maximum(typ), + ] + .into_iter() + .chain(self.comparators(typ)) + .chain( + if matching_op { + Some( + [ + self.symmetric_binary(Operator::QueEQ, typ), + self.symmetric_binary(Operator::QueNE, typ), + self.symmetric_binary(Operator::QueGT, typ), + self.symmetric_binary(Operator::QueGTE, typ), + self.symmetric_binary(Operator::QueLT, typ), + self.symmetric_binary(Operator::QueLTE, typ), + ] + .into_iter(), + ) + } else { + None + } + .into_iter() + .flatten(), + ) + } + + pub fn record_implicits(&self, typ: TypeEnt<'a>) -> impl Iterator> { + [ + self.comparison(Operator::EQ, typ), + self.comparison(Operator::NE, typ), + ] + .into_iter() + } + + fn concatenations( + &self, + array_type: TypeEnt<'a>, + elem_type: TypeEnt<'a>, + ) -> impl Iterator> { + [ + self.binary( + Operator::Concat, + array_type, + array_type, + elem_type, + array_type, + ), + self.binary( + Operator::Concat, + array_type, + elem_type, + array_type, + array_type, + ), + self.symmetric_binary(Operator::Concat, array_type), + self.binary( + Operator::Concat, + array_type, + elem_type, + elem_type, + array_type, + ), + ] + .into_iter() + } + + pub fn array_implicits( + &self, + typ: TypeEnt<'a>, + matching_op: bool, + ) -> impl Iterator> { + let Type::Array { + indexes, elem_type, .. + } = typ.kind() + else { + unreachable!("Must be array type") + }; + + let is_scalar = matches!( + elem_type.base().kind(), + Type::Integer | Type::Real | Type::Physical | Type::Enum(_) + ); + + let is_one_dimensional = indexes.len() == 1; + let is_character_elem = matches!(elem_type.base().kind(), Type::Enum(designators) if designators.iter().all(|des| matches!(des, Designator::Character(_)))); + + [ + self.comparison(Operator::EQ, typ), + self.comparison(Operator::NE, typ), + ] + .into_iter() + .chain(if is_one_dimensional && is_character_elem { + // To string is only defined for 1d array types with character elements + Some(self.create_to_string(typ)).into_iter() + } else { + None.into_iter() + }) + .chain( + (if is_one_dimensional { + Some(self.concatenations(typ, *elem_type)) + } else { + None + }) + .into_iter() + .flatten(), + ) + .chain( + (if is_scalar { + Some( + [ + self.comparison(Operator::GT, typ), + self.comparison(Operator::GTE, typ), + self.comparison(Operator::LT, typ), + self.comparison(Operator::LTE, typ), + self.elementwise_min_or_maximum("MINIMUM", typ, *elem_type), + self.elementwise_min_or_maximum("MAXIMUM", typ, *elem_type), + ] + .into_iter(), + ) + } else { + None + }) + .into_iter() + .flatten(), + ) + .chain( + if matching_op { + Some( + [ + self.binary(Operator::QueEQ, typ, typ, typ, *elem_type), + self.binary(Operator::QueNE, typ, typ, typ, *elem_type), + self.binary(Operator::QueGT, typ, typ, typ, *elem_type), + self.binary(Operator::QueGTE, typ, typ, typ, *elem_type), + self.binary(Operator::QueLT, typ, typ, typ, *elem_type), + self.binary(Operator::QueLTE, typ, typ, typ, *elem_type), + ] + .into_iter(), + ) + } else { + None + } + .into_iter() + .flatten(), + ) + } + + pub fn access_implicits(&self, typ: TypeEnt<'a>) -> impl Iterator> { + [ + self.deallocate(typ), + self.comparison(Operator::EQ, typ), + self.comparison(Operator::NE, typ), + ] + .into_iter() + } + + // Return the implicit things defined at the end of the standard packge + pub fn end_of_package_implicits( + &self, + region: &mut Region<'a>, + diagnostics: &mut dyn DiagnosticHandler, + ) { + { + let time = self.time(); + let to_string = self.create_to_string(time); + + unsafe { + self.arena.add_implicit(time.id(), to_string); + }; + region.add(to_string, diagnostics); + } + + for typ in [self.bit(), self.boolean()] { + let implicits = [ + self.symmetric_binary(Operator::And, typ), + self.symmetric_binary(Operator::Or, typ), + self.symmetric_binary(Operator::Nand, typ), + self.symmetric_binary(Operator::Nor, typ), + self.symmetric_binary(Operator::Xor, typ), + self.symmetric_binary(Operator::Xnor, typ), + self.symmetric_unary(Operator::Not, typ), + ] + .into_iter(); + + for ent in implicits { + unsafe { + self.arena.add_implicit(typ.id(), ent); + }; + region.add(ent, diagnostics); + } + } + + for (styp, atyp) in [ + (self.boolean(), self.boolean_vector()), + (self.bit(), self.bit_vector()), + ] { + let ops = [ + Operator::And, + Operator::Or, + Operator::Nand, + Operator::Nor, + Operator::Xor, + Operator::Xnor, + Operator::Not, + ]; + + let implicits = ops.iter().flat_map(|op| { + let op = *op; + [ + // A op A -> A + self.symmetric_binary(op, atyp), + if op == Operator::Not { + // op A -> A + self.unary(op, atyp, atyp) + } else { + // op A -> S + self.unary(op, atyp, styp) + }, + // A op S -> A + self.binary(op, atyp, atyp, styp, atyp), + // S op A -> A + self.binary(op, atyp, styp, atyp, atyp), + ] + .into_iter() + }); + + for ent in implicits { + // This is safe because the standard package is analyzed in a single thread + unsafe { + self.arena.add_implicit(atyp.id(), ent); + }; + region.add(ent, diagnostics); + } + } + + // Predefined overloaded TO_STRING operations + // function TO_STRING (VALUE: REAL; DIGITS: NATURAL) return STRING; + { + let real = self.real(); + let natural = self.natural(); + let string = self.string(); + + let ent = self + .implicit_subpgm( + real, + self.ident("TO_STRING"), + [ + ( + self.ident("VALUE"), + AnyEntKind::Object(Object::const_param(Subtype::new(real))), + ), + ( + self.ident("DIGITS"), + AnyEntKind::Object(Object::const_param(Subtype::new(natural))), + ), + ], + Some(string), + ) + .into(); + + // This is safe because the standard package is analyzed in a single thread + unsafe { + self.arena.add_implicit(real.id(), ent); + }; + region.add(ent, diagnostics); + } + + // function TO_STRING (VALUE: REAL; FORMAT: STRING) return STRING; + { + let real = self.real(); + let string = self.string(); + + let ent = self + .implicit_subpgm( + real, + self.ident("TO_STRING"), + [ + ( + self.ident("VALUE"), + AnyEntKind::Object(Object::const_param(Subtype::new(real))), + ), + ( + self.ident("FORMAT"), + AnyEntKind::Object(Object::const_param(Subtype::new(string))), + ), + ], + Some(string), + ) + .into(); + + // This is safe because the standard package is analyzed in a single thread + unsafe { + self.arena.add_implicit(real.id(), ent); + }; + region.add(ent, diagnostics); + } + + // function TO_STRING (VALUE: TIME; UNIT: TIME) return STRING + { + let time = self.time(); + let string = self.string(); + + let ent = self + .implicit_subpgm( + time, + self.ident("TO_STRING"), + [ + ( + self.ident("VALUE"), + AnyEntKind::Object(Object::const_param(Subtype::new(time))), + ), + ( + self.ident("UNIT"), + AnyEntKind::Object(Object::const_param(Subtype::new(time))), + ), + ], + Some(string), + ) + .into(); + + // This is safe because the standard package is analyzed in a single thread + unsafe { + self.arena.add_implicit(time.id(), ent); + }; + region.add(ent, diagnostics); + } + + // Special TO_STRING variants for bit-vector + { + let typ = self.bit_vector(); + let to_string = typ.implicits.iter().find(|ent| matches!(ent.designator(), Designator::Identifier(ident) if ident.name_utf8() == "TO_STRING")).unwrap(); + + let to_bstring = self.arena.alloc( + self.ident("TO_BSTRING"), + None, + Related::ImplicitOf(typ.into()), + AnyEntKind::Overloaded(Overloaded::Alias( + OverloadedEnt::from_any(to_string).unwrap(), + )), + to_string.decl_pos().cloned(), + to_string.src_span, + Some(self.source()), + ); + + let to_binary_string = self.arena.alloc( + self.ident("TO_BINARY_STRING"), + None, + Related::ImplicitOf(typ.into()), + AnyEntKind::Overloaded(Overloaded::Alias( + OverloadedEnt::from_any(to_string).unwrap(), + )), + to_string.decl_pos().cloned(), + to_string.src_span, + Some(self.source()), + ); + + let to_ostring = self.to_x_string("TO_OSTRING", typ); + + let to_octal_string = self.arena.alloc( + self.ident("TO_OCTAL_STRING"), + None, + Related::ImplicitOf(typ.into()), + AnyEntKind::Overloaded(Overloaded::Alias( + OverloadedEnt::from_any(to_ostring).unwrap(), + )), + to_string.decl_pos().cloned(), + to_string.src_span, + Some(self.source()), + ); + + let to_hstring = self.to_x_string("TO_HSTRING", typ); + let to_hex_string = self.arena.alloc( + self.ident("TO_HEX_STRING"), + None, + Related::ImplicitOf(typ.into()), + AnyEntKind::Overloaded(Overloaded::Alias( + OverloadedEnt::from_any(to_hstring).unwrap(), + )), + to_string.decl_pos().cloned(), + to_string.src_span, + Some(self.source()), + ); + + let implicits = [ + to_bstring, + to_binary_string, + to_ostring, + to_octal_string, + to_hstring, + to_hex_string, + ]; + + for ent in implicits { + // This is safe because the standard package is analyzed in a single thread + unsafe { + self.arena.add_implicit(typ.id(), ent); + }; + region.add(ent, diagnostics); + } + } + + // ?? operator for bit + { + let typ = self.bit(); + let qq = self.unary(Operator::QueQue, typ, self.boolean()); + + unsafe { + self.arena.add_implicit(typ.id(), qq); + }; + region.add(qq, diagnostics); + } + } +} diff --git a/vhdl_lang/src/analysis/static_expression.rs b/vhdl_lang/src/analysis/static_expression.rs new file mode 100644 index 0000000..ad255c3 --- /dev/null +++ b/vhdl_lang/src/analysis/static_expression.rs @@ -0,0 +1,481 @@ +use crate::analysis::static_expression::BitStringConversionError::EmptySignedExpansion; +use crate::ast::{BaseSpecifier, BitString}; +use crate::Latin1String; +use itertools::Itertools; +use std::cmp::Ordering; +use std::iter; + +/// returns whether `byte` is an odd number when interpreted as decimal. +/// byte must be between '0' and '9', but it is up to the caller to enforce this. +fn byte_is_odd_decimal(byte: u8) -> bool { + (byte - b'0') % 2 == 1 +} + +/// Converts a decimal string (i.e. "123") to a binary string (i.e. "1111011"). +/// +/// When there are illegal characters in the string (i.e. non-decimal characters), +/// returns an `Err` with the position of the first character. +/// +/// # Special cases +/// - For an empty string, return a single string containing '0' +/// - For a string with zeros, return a single string containing '0' +/// - For a string that is padded with zeros, return a string without the padding. If the +/// String without the padding is empty, rule 1 applies. +pub(crate) fn decimal_str_to_binary_str( + value: &[u8], +) -> Result { + /// Divides `value` by two where `value` is a vector of u8's representing decimals. + /// Returns an empty string when `value` is zero + fn str_divide_by_2(value: Vec) -> Vec { + let mut new_s: Vec = Vec::new(); + let mut add_next = 0; + + for ch in value { + let new_digit = (ch + b'0') / 2 + add_next; + new_s.push(new_digit); + add_next = if byte_is_odd_decimal(ch) { 5 } else { 0 }; + } + + // remove the first element if it's '0' + if new_s.first() == Some(&b'0') { + new_s.drain(..1); + } + + new_s + } + + if let Some(idx) = value.iter().position(|b| *b < b'0' || *b > b'9') { + return Err(BitStringConversionError::IllegalDecimalCharacter(idx)); + } + + let mut num: Vec = value.iter().copied().skip_while(|el| *el == b'0').collect(); + + if num.is_empty() { + return Ok(Latin1String::new(b"0")); + } + + let mut stack: Vec = Vec::new(); + + while !num.is_empty() { + if byte_is_odd_decimal(*num.last().unwrap()) { + stack.push(b'1'); + } else { + stack.push(b'0'); + } + num = str_divide_by_2(num); + } + stack.reverse(); + + Ok(Latin1String::from_vec(stack)) +} + +#[test] +fn test_decimal_to_binary() { + let test_cases = [ + ("", "0"), + ("0", "0"), + ("000", "0"), + ("001", "1"), + ("1", "1"), + ("12345", "11000000111001"), + ( + "123456781234567812345678", + "11010001001001001101100000011011011101100011101100101101101011110111101001110", + ), + ]; + + for (dec, bin) in test_cases { + assert_eq!( + decimal_str_to_binary_str(dec.as_bytes()), + Ok(Latin1String::from_utf8_unchecked(bin)) + ); + } +} + +impl BaseSpecifier { + /// Returns whether this base specifier represents a signed value + /// (i.e. `SX` for signed hexadecimal) or an unsigned value + /// (i.e. `UX` or `X` for unsigned hexadecimal) + pub fn is_signed(&self) -> bool { + match self { + BaseSpecifier::SX | BaseSpecifier::SO | BaseSpecifier::SB => true, + BaseSpecifier::B + | BaseSpecifier::UB + | BaseSpecifier::O + | BaseSpecifier::UO + | BaseSpecifier::X + | BaseSpecifier::UX + | BaseSpecifier::D => false, + } + } + + /// Get the digits that are obtained by replacing `byte` with the + /// appropriate sequence of characters as defined in the standard (section 15.8). + /// + /// # Special Cases + /// If the base specifier is `D`, i.e. decimal, return the byte itself (wrapped as array) + /// + /// # Example + /// ``` + /// use vhdl_lang::ast::BaseSpecifier; + /// + /// let digits: Vec = BaseSpecifier::UX.get_extended_digits(b'C'); + /// assert_eq!(digits, Vec::from("1100")); + /// + /// let digits: Vec = BaseSpecifier::O.get_extended_digits(b'F'); + /// assert_eq!(digits, Vec::from("FFF")) + /// ``` + pub fn get_extended_digits(&self, byte: u8) -> Vec { + match self { + // For O, UO and SO, the values 1-7 are replaced. + // All other values are left as-is. + BaseSpecifier::O | BaseSpecifier::UO | BaseSpecifier::SO => match byte { + b'0' => Vec::from("000"), + b'1' => Vec::from("001"), + b'2' => Vec::from("010"), + b'3' => Vec::from("011"), + b'4' => Vec::from("100"), + b'5' => Vec::from("101"), + b'6' => Vec::from("110"), + b'7' => Vec::from("111"), + _ => vec![byte; 3], + }, + // For U, UX and SX, the values 1-9 and A-F are replaced. + // All other values are left as-is. + BaseSpecifier::X | BaseSpecifier::UX | BaseSpecifier::SX => match byte { + b'0' => Vec::from("0000"), + b'1' => Vec::from("0001"), + b'2' => Vec::from("0010"), + b'3' => Vec::from("0011"), + b'4' => Vec::from("0100"), + b'5' => Vec::from("0101"), + b'6' => Vec::from("0110"), + b'7' => Vec::from("0111"), + b'8' => Vec::from("1000"), + b'9' => Vec::from("1001"), + b'A' | b'a' => Vec::from("1010"), + b'B' | b'b' => Vec::from("1011"), + b'C' | b'c' => Vec::from("1100"), + b'D' | b'd' => Vec::from("1101"), + b'E' | b'e' => Vec::from("1110"), + b'F' | b'f' => Vec::from("1111"), + _ => vec![byte; 4], + }, + // Binary values are simply the values left as they are. + BaseSpecifier::B | BaseSpecifier::UB | BaseSpecifier::SB | BaseSpecifier::D => { + vec![byte] + } + } + } +} + +/// Represents errors that occur when converting a bit string to a regular string +#[derive(PartialEq, Eq, Clone, Debug)] +pub(crate) enum BitStringConversionError { + /// Illegal decimal character encountered while converting a decimal bit string (i.e. D"12AFFE") + /// The `usize` argument represent the position for the first illegal character in the + /// bit_string's `value` string, (i.e. 2 for the example above) + IllegalDecimalCharacter(usize), + /// Signals that when converting a value and truncating, information would be lost. + /// # Example + /// 5B"111111" => The first '0' would be lost + /// 8SX"0FF" => The bit-string is positive but would be converted to a negative value + /// The `usize` argument is the index of the first character that cannot be truncated. + /// The `Latin1String` argument is the expanded (erroneous) String + IllegalTruncate(usize, Latin1String), + /// Trying to expand an empty signed expression, i.e. + /// SX"" + EmptySignedExpansion, +} + +/// Converts a `BitString` to a `Latin1String` respecting the replacement values defined in LRM +/// Returns the string as Latin1String when successful and a `BitStringConversionError` else +/// 15.8 Bit string literals +pub(crate) fn bit_string_to_string( + bit_string: &BitString, +) -> Result { + // Simplifies the bit string by removing all occurrences of the underscore + // character + let simplified_value: Vec = bit_string + .value + .bytes + .clone() + .into_iter() + .filter(|&b| b != b'_') + .collect(); + + // For empty signed bit-strings it is unclear what the expanded value should be, + // according to the reference: + // For example, 2SB"" could be + // 1) A string containing '0's, i.e. "00" + // 2) An error + // According to the standard, the padding value should be the leftmost character in the string + // but an empty string does not have a leftmost character. + if simplified_value.is_empty() { + return match bit_string.length { + None => Ok(Latin1String::empty()), + Some(value) => { + if bit_string.base.is_signed() { + Err(EmptySignedExpansion) + } else { + Ok(Latin1String::from_vec( + iter::repeat(b'0').take(value as usize).collect_vec(), + )) + } + } + }; + } + + let mut extended_value = Vec::new(); + + if bit_string.base == BaseSpecifier::D { + match decimal_str_to_binary_str(&simplified_value) { + Err(e) => return Err(e), + Ok(binary_string) => extended_value = binary_string.bytes, + } + } else { + for ch in simplified_value { + extended_value.append(&mut bit_string.base.get_extended_digits(ch)); + } + } + + // append, truncate or leave the bit-string dependent on the user-specified length + match bit_string.length { + None => Ok(Latin1String::from_vec(extended_value)), + Some(length) => { + let length = length as usize; + match length.cmp(&extended_value.len()) { + Ordering::Equal => Ok(Latin1String::from_vec(extended_value)), + Ordering::Less => { + let pivot = extended_value.len() - length; + let first_elements = &extended_value[..pivot]; + let last_elements = &extended_value[pivot..]; + // This char is allowed and may be truncated from the vector + let allowed_char = if bit_string.base.is_signed() { + last_elements[0] + } else { + b'0' + }; + + let idx = first_elements + .iter() + .rev() + .position(|el| *el != allowed_char); + match idx { + Some(value) => { + let real_idx = last_elements.len() + value - 1; + let erroneous_string = Latin1String::from_vec(extended_value); + Err(BitStringConversionError::IllegalTruncate( + real_idx, + erroneous_string, + )) + } + None => Ok(Latin1String::new(last_elements)), + } + } + Ordering::Greater => { + let pad_char = if bit_string.base.is_signed() { + extended_value[0] + } else { + b'0' + }; + let pad_vector = iter::repeat(pad_char) + .take(length - extended_value.len()) + .chain(extended_value) + .collect_vec(); + Ok(Latin1String::from_vec(pad_vector)) + } + } + } + } +} + +#[cfg(test)] +mod test_mod { + use crate::analysis::static_expression::{bit_string_to_string, BitStringConversionError}; + use crate::ast::{BaseSpecifier, BitString}; + use crate::Latin1String; + + impl BitString { + fn new(length: Option, base: BaseSpecifier, value: &str) -> BitString { + BitString { + length, + base, + value: Latin1String::from_utf8_unchecked(value), + } + } + } + + #[test] + fn an_empty_bit_string_converts_to_an_empty_string() { + let all_base_specifiers = [ + BaseSpecifier::O, + BaseSpecifier::UO, + BaseSpecifier::SO, + BaseSpecifier::X, + BaseSpecifier::UX, + BaseSpecifier::SX, + BaseSpecifier::B, + BaseSpecifier::UB, + BaseSpecifier::SB, + BaseSpecifier::D, + ]; + for base_specifier in all_base_specifiers { + assert_eq!( + bit_string_to_string(&BitString::new(None, base_specifier, "")).unwrap(), + Latin1String::empty() + ) + } + } + + #[test] + fn test_illegal_decimal_character() { + assert_eq!( + bit_string_to_string(&BitString::new(None, BaseSpecifier::D, "12AFFE")), + Err(BitStringConversionError::IllegalDecimalCharacter(2)) + ); + + assert_eq!( + bit_string_to_string(&BitString::new(None, BaseSpecifier::D, "?")), + Err(BitStringConversionError::IllegalDecimalCharacter(0)) + ); + + assert_eq!( + bit_string_to_string(&BitString::new(None, BaseSpecifier::D, "78234+")), + Err(BitStringConversionError::IllegalDecimalCharacter(5)) + ); + } + + #[test] + fn test_decimal_conversion() { + let test_cases = [ + (BitString::new(None, BaseSpecifier::D, ""), ""), + (BitString::new(None, BaseSpecifier::D, "0"), "0"), + (BitString::new(None, BaseSpecifier::D, "00"), "0"), + (BitString::new(None, BaseSpecifier::D, "000"), "0"), + (BitString::new(None, BaseSpecifier::D, "1"), "1"), + (BitString::new(None, BaseSpecifier::D, "01"), "1"), + (BitString::new(None, BaseSpecifier::D, "10"), "1010"), + ( + BitString::new(None, BaseSpecifier::D, "164824"), + "101000001111011000", + ), + ( + BitString::new(None, BaseSpecifier::D, "123456781234567812345678"), + "11010001001001001101100000011011011101100011101100101101101011110111101001110", + ), + ]; + + for (bit_string, result_string) in test_cases { + assert_eq!( + bit_string_to_string(&bit_string).unwrap(), + Latin1String::from_utf8_unchecked(result_string) + ) + } + } + + #[test] + fn test_illegal_truncate_position() { + assert_eq!( + bit_string_to_string(&BitString::new(Some(8), BaseSpecifier::SX, "0FF")), + Err(BitStringConversionError::IllegalTruncate( + 7, + Latin1String::new(b"000011111111") + )) + ); + + assert_eq!( + bit_string_to_string(&BitString::new(Some(8), BaseSpecifier::SX, "1FF")), + Err(BitStringConversionError::IllegalTruncate( + 8, + Latin1String::new(b"000111111111") + )) + ); + + assert_eq!( + bit_string_to_string(&BitString::new(Some(8), BaseSpecifier::SX, "3FF")), + Err(BitStringConversionError::IllegalTruncate( + 9, + Latin1String::new(b"001111111111") + )) + ); + } + + // Examples defined in 15.8 + #[test] + fn spec_examples() { + let test_cases = [ + ( + BitString::new(None, BaseSpecifier::B, "1111_1111_1111"), + "111111111111", + ), + ( + BitString::new(None, BaseSpecifier::X, "FFF"), + "111111111111", + ), + (BitString::new(None, BaseSpecifier::O, "777"), "111111111"), + ( + BitString::new(None, BaseSpecifier::X, "777"), + "011101110111", + ), + ( + BitString::new(None, BaseSpecifier::B, "XXXX_01LH"), + "XXXX01LH", + ), + (BitString::new(None, BaseSpecifier::UO, "27"), "010111"), + (BitString::new(None, BaseSpecifier::SX, "3W"), "0011WWWW"), + (BitString::new(None, BaseSpecifier::D, "35"), "100011"), + ( + BitString::new(Some(12), BaseSpecifier::UB, "X1"), + "0000000000X1", + ), + ( + BitString::new(Some(12), BaseSpecifier::SB, "X1"), + "XXXXXXXXXXX1", + ), + ( + BitString::new(Some(12), BaseSpecifier::UX, "F-"), + "00001111----", + ), + ( + BitString::new(Some(12), BaseSpecifier::SX, "F-"), + "11111111----", + ), + ( + BitString::new(Some(12), BaseSpecifier::UX, "000WWW"), + "WWWWWWWWWWWW", + ), + ( + BitString::new(Some(12), BaseSpecifier::SX, "FFFC00"), + "110000000000", + ), + ]; + + let error_cases = [ + BitString::new(Some(8), BaseSpecifier::D, "511"), + BitString::new(Some(8), BaseSpecifier::UO, "477"), + BitString::new(Some(8), BaseSpecifier::SX, "0FF"), + BitString::new(Some(8), BaseSpecifier::SX, "FXX"), + ]; + + for bit_string in error_cases { + assert!(bit_string_to_string(&bit_string).err().is_some()); + } + + for (bit_string, result_string) in test_cases { + assert_eq!( + bit_string_to_string(&bit_string).unwrap(), + Latin1String::from_utf8_unchecked(result_string) + ) + } + } + + // Issue 332 + #[test] + fn underscore_in_decimal_bit_string() { + assert!( + bit_string_to_string(&BitString::new(Some(32), BaseSpecifier::D, "1_000_000_000")) + .is_ok() + ); + } +} diff --git a/vhdl_lang/src/analysis/subprogram.rs b/vhdl_lang/src/analysis/subprogram.rs new file mode 100644 index 0000000..abfabbc --- /dev/null +++ b/vhdl_lang/src/analysis/subprogram.rs @@ -0,0 +1,497 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use super::names::*; +use super::*; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::{Signature, *}; +use crate::{ast, HasTokenSpan}; +use analyze::*; +use itertools::Itertools; +use vhdl_lang::TokenSpan; + +impl<'a, 't> AnalyzeContext<'a, 't> { + fn subprogram_header( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + header: &mut SubprogramHeader, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult> { + let mut region = Region::default(); + for decl in header.generic_list.items.iter_mut() { + if let Some(ents) = + as_fatal(self.analyze_interface_declaration(scope, parent, decl, diagnostics))? + { + for ent in ents { + region.add(ent, diagnostics); + scope.add(ent, diagnostics); + } + } + } + self.analyze_map_aspect(scope, &mut header.map_aspect, diagnostics)?; + Ok(region) + } + + pub(crate) fn subprogram_body( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + body: &mut SubprogramBody, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let (subpgm_region, subpgm_ent) = match as_fatal(self.subprogram_specification( + scope, + parent, + &mut body.specification, + body.span, + Overloaded::Subprogram, + diagnostics, + ))? { + Some(r) => r, + None => { + return Ok(()); + } + }; + + scope.add(subpgm_ent, diagnostics); + + self.define_labels_for_sequential_part( + &subpgm_region, + subpgm_ent, + &mut body.statements, + diagnostics, + )?; + self.analyze_declarative_part( + &subpgm_region, + subpgm_ent, + &mut body.declarations, + diagnostics, + )?; + + self.analyze_sequential_part( + &subpgm_region, + subpgm_ent, + &mut body.statements, + diagnostics, + )?; + Ok(()) + } + + pub(crate) fn subprogram_specification( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + subprogram: &mut SubprogramSpecification, + span: TokenSpan, + to_kind: impl Fn(Signature<'a>) -> Overloaded<'a>, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult<(Scope<'a>, EntRef<'a>)> { + let subpgm_region = scope.nested(); + let ent = self.arena.explicit( + subprogram + .subpgm_designator() + .item + .clone() + .into_designator(), + parent, + AnyEntKind::Overloaded(to_kind(Signature::new(FormalRegion::new_params(), None))), + Some(subprogram.subpgm_designator().pos(self.ctx)), + span, + Some(self.source()), + ); + + let (signature, generic_map) = match subprogram { + SubprogramSpecification::Function(fun) => { + let generic_map = if let Some(header) = &mut fun.header { + Some(self.subprogram_header(&subpgm_region, ent, header, diagnostics)?) + } else { + None + }; + let params = if let Some(parameter_list) = &mut fun.parameter_list { + self.analyze_interface_list(&subpgm_region, ent, parameter_list, diagnostics) + } else { + Ok(FormalRegion::new_params()) + }; + let return_type = self.type_name( + &subpgm_region, + fun.return_type.span, + &mut fun.return_type.item, + diagnostics, + ); + (Signature::new(params?, Some(return_type?)), generic_map) + } + SubprogramSpecification::Procedure(procedure) => { + let generic_map = if let Some(header) = &mut procedure.header { + Some(self.subprogram_header(&subpgm_region, ent, header, diagnostics)?) + } else { + None + }; + let params = if let Some(parameter_list) = &mut procedure.parameter_list { + self.analyze_interface_list(&subpgm_region, ent, parameter_list, diagnostics) + } else { + Ok(FormalRegion::new_params()) + }; + (Signature::new(params?, None), generic_map) + } + }; + + let mut kind = to_kind(signature); + if let Some(map) = generic_map { + match kind { + Overloaded::SubprogramDecl(signature) => { + kind = Overloaded::UninstSubprogramDecl(signature, map) + } + Overloaded::Subprogram(signature) => { + kind = Overloaded::UninstSubprogram(signature, map) + } + _ => unreachable!(), + } + } + + match kind { + Overloaded::Subprogram(_) => { + let declared_by = + self.find_subpgm_specification(scope, subprogram, kind.signature()); + + if let Some(declared_by) = declared_by { + unsafe { + ent.set_declared_by(declared_by.into()); + } + } + } + Overloaded::UninstSubprogram(_, _) => { + let declared_by = + self.find_uninst_subpgm_specification(scope, subprogram, kind.signature()); + + if let Some(declared_by) = declared_by { + unsafe { + ent.set_declared_by(declared_by.into()); + } + } + } + _ => {} + } + + unsafe { + ent.set_kind(AnyEntKind::Overloaded(kind)); + } + subprogram.set_decl_id(ent.id()); + Ok((subpgm_region, ent)) + } + + pub fn resolve_signature( + &self, + scope: &Scope<'a>, + signature: &mut WithTokenSpan, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let (args, return_type) = match &mut signature.item { + ast::Signature::Function(ref mut args, ref mut ret) => { + let args: Vec<_> = args + .iter_mut() + .map(|arg| self.type_name(scope, arg.span, &mut arg.item, diagnostics)) + .collect(); + let return_type = self.type_name(scope, ret.span, &mut ret.item, diagnostics); + (args, Some(return_type)) + } + ast::Signature::Procedure(args) => { + let args: Vec<_> = args + .iter_mut() + .map(|arg| self.type_name(scope, arg.span, &mut arg.item, diagnostics)) + .collect(); + (args, None) + } + }; + + let mut params = Vec::with_capacity(args.len()); + for arg in args { + params.push(arg?.base()); + } + + if let Some(return_type) = return_type { + Ok(SignatureKey::new( + params, + Some(return_type?.base_type().base()), + )) + } else { + Ok(SignatureKey::new(params, None)) + } + } + + /// Analyze a generic subprogram instance, i.e., + /// ```vhdl + /// procedure my_proc is new my_proc generic map (T => std_logic); + /// ``` + /// + /// # Arguments + /// + /// * `scope` - The scope that this instance was declared in + /// * `inst_subprogram_ent` - A reference to the instantiated subprogram entity. + /// Used to set the parent reference of the signature + /// * `uninst_name` - The [ResolvedName] of the uninstantiated subprogram + /// * `instance` - A reference to the AST element of the subprogram instantiation + /// * `diagnostics` - The diagnostics handler + /// + /// # Returns + /// The signature after applying the optional map aspect of the uninstantiated subprogram + pub(crate) fn generic_subprogram_instance( + &self, + scope: &Scope<'a>, + inst_subprogram_ent: &EntRef<'a>, + uninst_name: &ResolvedName<'a>, + instance: &mut SubprogramInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let uninstantiated_subprogram = + self.resolve_uninstantiated_subprogram(scope, uninst_name, instance, diagnostics)?; + self.check_instantiated_subprogram_kind_matches_declared( + &uninstantiated_subprogram, + instance, + diagnostics, + ); + instance + .subprogram_name + .item + .set_unique_reference(&uninstantiated_subprogram); + let region = match uninstantiated_subprogram.kind() { + Overloaded::UninstSubprogramDecl(_, region) => region, + Overloaded::UninstSubprogram(_, region) => region, + _ => unreachable!(), + }; + + let nested = scope.nested(); + + match as_fatal(self.generic_instance( + inst_subprogram_ent, + scope, + instance.ident.tree.pos(self.ctx), + region, + &mut instance.generic_map, + diagnostics, + ))? { + None => Ok(uninstantiated_subprogram.signature().clone()), + Some((_, mapping)) => { + match self.map_signature( + Some(inst_subprogram_ent), + &mapping, + uninstantiated_subprogram.signature(), + &nested, + ) { + Ok(signature) => Ok(signature), + Err((err, code)) => { + let mut diag = + Diagnostic::new(instance.ident.tree.pos(self.ctx), err, code); + if let Some(pos) = uninstantiated_subprogram.decl_pos() { + diag.add_related(pos, "When instantiating this declaration"); + } + diagnostics.push(diag); + Err(EvalError::Unknown) + } + } + } + } + } + + /// Given a `ResolvedName` and the subprogram instantiation, + /// find the uninstantiated subprogram that the resolved name references. + /// Return that resolved subprogram, if it exists, else return an `Err` + fn resolve_uninstantiated_subprogram( + &self, + scope: &Scope<'a>, + name: &ResolvedName<'a>, + instantiation: &mut SubprogramInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let signature_key = match &mut instantiation.signature { + None => None, + Some(ref mut signature) => Some(( + self.resolve_signature(scope, signature, diagnostics)?, + signature.pos(self.ctx), + )), + }; + let overloaded_ent = match name { + ResolvedName::Overloaded(_, overloaded) => { + let choices = overloaded + .entities() + .filter(|ent| ent.is_uninst_subprogram()) + .collect_vec(); + if choices.is_empty() { + diagnostics.add( + instantiation.ident.tree.pos(self.ctx), + format!( + "{} does not denote an uninstantiated subprogram", + name.describe() + ), + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } else if choices.len() == 1 { + // There is only one possible candidate + let ent = choices[0]; + // If the instantiated program has a signature, check that it matches + // that of the uninstantiated subprogram + if let Some((key, pos)) = signature_key { + match overloaded.get(&SubprogramKey::Uninstantiated(key)) { + None => { + diagnostics.add( + pos.clone(), + format!( + "Signature does not match the the signature of {}", + ent.describe() + ), + ErrorCode::SignatureMismatch, + ); + return Err(EvalError::Unknown); + } + Some(_) => ent, + } + } else { + ent + } + } else if let Some((key, _)) = signature_key { + // There are multiple candidates + // but there is a signature that we can try to resolve + if let Some(resolved_ent) = + overloaded.get(&SubprogramKey::Uninstantiated(key.clone())) + { + resolved_ent + } else { + diagnostics.add( + instantiation.subprogram_name.pos(self.ctx), + format!( + "No uninstantiated subprogram exists with signature {}", + key.describe() + ), + ErrorCode::Unresolved, + ); + return Err(EvalError::Unknown); + } + } else { + // There are multiple candidates + // and there is no signature to resolve + let mut err = Diagnostic::new( + instantiation.subprogram_name.pos(self.ctx), + format!("Ambiguous instantiation of '{}'", overloaded.designator()), + ErrorCode::AmbiguousInstantiation, + ); + for ent in choices { + if let Some(pos) = &ent.decl_pos { + err.add_related(pos.clone(), format!("Might be {}", ent.describe())) + } + } + diagnostics.push(err); + return Err(EvalError::Unknown); + } + } + _ => { + diagnostics.add( + instantiation.subprogram_name.pos(self.ctx), + format!( + "{} does not denote an uninstantiated subprogram", + name.describe() + ), + ErrorCode::MismatchedKinds, + ); + return Err(EvalError::Unknown); + } + }; + if overloaded_ent.is_uninst_subprogram() { + Ok(overloaded_ent) + } else { + diagnostics.add( + instantiation.subprogram_name.pos(self.ctx), + format!("{} cannot be instantiated", overloaded_ent.describe()), + ErrorCode::MismatchedKinds, + ); + Err(EvalError::Unknown) + } + } + + /// Checks that an instantiated subprogram kind matches the declared subprogram. + /// For instance, when a subprogram was instantiated using + /// ```vhdl + /// function my_func is new proc; + /// ``` + /// where proc is + /// ```vhdl + /// procedure proc is + /// ... + /// ``` + /// + /// This function will push an appropriate diagnostic. + fn check_instantiated_subprogram_kind_matches_declared( + &self, + ent: &OverloadedEnt<'_>, + instance: &SubprogramInstantiation, + diagnostics: &mut dyn DiagnosticHandler, + ) { + let err_msg = if ent.is_function() && instance.kind != SubprogramKind::Function { + Some("Instantiating function as procedure") + } else if ent.is_procedure() && instance.kind != SubprogramKind::Procedure { + Some("Instantiating procedure as function") + } else { + None + }; + if let Some(msg) = err_msg { + let mut err = Diagnostic::new( + self.ctx.get_pos(instance.get_start_token()), + msg, + ErrorCode::MismatchedSubprogramInstantiation, + ); + if let Some(pos) = ent.decl_pos() { + err.add_related(pos, format!("{} declared here", ent.describe())); + } + diagnostics.push(err) + } + } + + fn find_subpgm_specification( + &self, + scope: &Scope<'a>, + decl: &SubprogramSpecification, + signature: &Signature<'_>, + ) -> Option> { + let des = decl.subpgm_designator().item.clone().into_designator(); + + if let Some(NamedEntities::Overloaded(overloaded)) = scope.lookup_immediate(&des) { + let ent = overloaded.get(&SubprogramKey::Normal(signature.key()))?; + + if ent.is_subprogram_decl() { + return Some(ent); + } + } + None + } + + fn find_uninst_subpgm_specification( + &self, + scope: &Scope<'a>, + decl: &SubprogramSpecification, + signature: &Signature<'_>, + ) -> Option> { + let des = decl.subpgm_designator().item.clone().into_designator(); + + if let Some(NamedEntities::Overloaded(overloaded)) = scope.lookup_immediate(&des) { + // Note: This does not work in common circumstances with a generic type parameter + // since the parameters of the declared subprogram and the subprogram with body + // point to two different type-ID's. For example: + // function foo generic (type F) return F; + // ^-- F has EntityId X + // function foo generic (type F) return F is ... end function foo; + // ^-- F has EntityId Y + // A future improvement must take this fact into account. + let ent = overloaded.get(&SubprogramKey::Uninstantiated(signature.key()))?; + + if ent.is_uninst_subprogram_decl() { + return Some(ent); + } + } + None + } +} diff --git a/vhdl_lang/src/analysis/target.rs b/vhdl_lang/src/analysis/target.rs new file mode 100644 index 0000000..b04210f --- /dev/null +++ b/vhdl_lang/src/analysis/target.rs @@ -0,0 +1,89 @@ +use super::analyze::*; +use super::scope::*; +use crate::ast::token_range::WithTokenSpan; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::*; +use vhdl_lang::TokenSpan; + +/// Analysis of assignment targets +/// +/// examples: +/// target <= 1; +/// target(0).elem := 1 +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn resolve_target( + &self, + scope: &Scope<'a>, + target: &mut WithTokenSpan, + assignment_type: AssignmentType, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + match target.item { + Target::Name(ref mut name) => { + self.resolve_target_name(scope, name, target.span, assignment_type, diagnostics) + } + Target::Aggregate(ref mut assocs) => { + self.analyze_aggregate(scope, assocs, diagnostics)?; + Err(EvalError::Unknown) + } + } + } + + pub fn resolve_target_name( + &self, + scope: &Scope<'a>, + target: &mut Name, + target_pos: TokenSpan, + assignment_type: AssignmentType, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + let object_name = self.resolve_object_name( + scope, + target_pos, + target, + "may not be the target of an assignment", + ErrorCode::MismatchedKinds, + diagnostics, + )?; + if !object_name.base.can_be_assigned_to() { + diagnostics.add( + target_pos.pos(self.ctx), + format!( + "{} may not be the target of an assignment", + object_name.base.describe_class() + ), + ErrorCode::MismatchedKinds, + ); + } else if !object_name.base.is_valid_assignment_type(assignment_type) { + diagnostics.add( + target_pos.pos(self.ctx), + format!( + "{} may not be the target of a {} assignment", + object_name.base.describe_class(), + assignment_type.to_str() + ), + ErrorCode::MismatchedKinds, + ); + } + Ok(object_name.type_mark()) + } +} + +#[derive(Copy, Clone)] +pub enum AssignmentType { + // Assignment with <= + Signal, + // Assignment with := + Variable, +} + +impl AssignmentType { + fn to_str(self) -> &'static str { + match self { + AssignmentType::Signal => "signal", + AssignmentType::Variable => "variable", + } + } +} diff --git a/vhdl_lang/src/analysis/tests/assignment_typecheck.rs b/vhdl_lang/src/analysis/tests/assignment_typecheck.rs new file mode 100644 index 0000000..0d8bdf3 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/assignment_typecheck.rs @@ -0,0 +1,682 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn overloaded_name_may_not_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + function foo1 return natural is + begin + return 0; + end; + + type enum_t is (foo2, enum_value); +begin + main : process + begin + foo1 := 1; + foo2 := 1; + end process; +end architecture; +", + ); + + let expected = vec![ + Diagnostic::mismatched_kinds( + code.s("foo1", 2), + "function foo1[return NATURAL] may not be the target of an assignment", + ), + Diagnostic::mismatched_kinds( + code.s("foo2", 2), + "foo2[return enum_t] may not be the target of an assignment", + ), + ]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn attribute_name_may_not_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + signal foo : boolean; +begin + main : process + begin + foo'stable := true; + end process; +end architecture; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::mismatched_kinds( + code.s("foo'stable", 1), + "signal 'stable' may not be the target of a variable assignment", + )], + ); +} + +#[test] +fn subprogram_call_may_not_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + function foo1(arg : natural) return natural; +end package; + +package body pkg is + function foo1(arg : natural) return natural is + begin + return 0; + end function; +end package body; + +entity ent is +end entity; + +architecture a of ent is + function foo2(arg : natural) return natural is + begin + return 0; + end function; +begin + main : process + begin + work.pkg.foo1(2) := 1; + foo2(2) := 1; + work.pkg.foo1(arg => 2) := 1; + foo2(arg => 2) := 1; + end process; +end architecture; +", + ); + + let expected = vec![ + Diagnostic::mismatched_kinds( + code.s1("work.pkg.foo1(2)"), + "Expression may not be the target of an assignment", + ), + Diagnostic::mismatched_kinds( + code.s1("foo2(2)"), + "Expression may not be the target of an assignment", + ), + Diagnostic::mismatched_kinds( + code.s1("work.pkg.foo1(arg => 2)"), + "Expression may not be the target of an assignment", + ), + Diagnostic::mismatched_kinds( + code.s1("foo2(arg => 2)"), + "Expression may not be the target of an assignment", + ), + ]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn constant_may_not_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + constant foo1 : natural := 0; + alias foo2 is foo1; +begin + main : process + begin + foo1 := 1; + foo2 := 1; + end process; +end architecture; +", + ); + + let expected = vec![ + Diagnostic::mismatched_kinds( + code.s("foo1", 3), + "constant 'foo1' may not be the target of an assignment", + ), + Diagnostic::mismatched_kinds( + code.s("foo2", 2), + "alias 'foo2' of constant may not be the target of an assignment", + ), + ]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn objects_may_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + signal foo1 : natural := 0; + alias foo2 is foo1; + shared variable foo3 : natural := 0; +begin + main : process + variable foo4 : natural := 0; + begin + foo1 <= 1; + foo2 <= 1; + foo3 := 1; + foo4 := 1; + end process; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn indexed_names_may_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + + type arr1_t is array (natural range 0 to 1) of natural; + type arr2_t is array (natural range 0 to 1, natural range 0 to 1) of natural; + type arr3_t is array (natural range 0 to 1) of arr1_t; + + signal foo1 : arr1_t; + signal foo2 : arr2_t; + signal foo3 : arr3_t; +begin + foo1(0) <= 0; + foo2(0, 0) <= 0; + foo3(0)(0) <= 0; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn interface_objects_may_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + procedure proc1(signal foo : out natural) is + begin + foo <= 1; + end; +begin + main : process + procedure proc2(variable foo : inout natural) is + begin + foo := 1; + end; + begin + end process; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn interface_constant_may_not_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin + main : process + procedure proc1(constant foo1 : natural) is + begin + foo1 := 1; + end; + procedure proc2(variable foo2 : in natural) is + begin + foo2 := 1; + end; + begin + end process; +end architecture; +", + ); + + let expected = vec![ + Diagnostic::mismatched_kinds( + code.s("foo1", 2), + "interface constant 'foo1' may not be the target of an assignment", + ), + Diagnostic::mismatched_kinds( + code.s("foo2", 2), + "interface variable 'foo2' of mode in may not be the target of an assignment", + ), + ]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn checks_signal_vs_variable_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + signal foo3 : natural; +begin + main : process + variable foo4 : natural; + + procedure proc1(signal foo1 : out natural) is + begin + foo1 := 1; + end; + procedure proc2(variable foo2 : out natural) is + begin + foo2 <= 1; + end; + begin + foo3 := 1; + foo4 <= 1; + end process; +end architecture; +", + ); + + let expected = vec![ + Diagnostic::mismatched_kinds( + code.s("foo1", 2), + "interface signal 'foo1' of mode out may not be the target of a variable assignment", + ), + Diagnostic::mismatched_kinds( + code.s("foo2", 2), + "interface variable 'foo2' of mode out may not be the target of a signal assignment", + ), + Diagnostic::mismatched_kinds( + code.s("foo3", 2), + "signal 'foo3' may not be the target of a variable assignment", + ), + Diagnostic::mismatched_kinds( + code.s("foo4", 2), + "variable 'foo4' may not be the target of a signal assignment", + ), + ]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn indexed_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + signal foo : natural; +begin + foo(0) <= 0; +end architecture; +", + ); + + let expected = vec![Diagnostic::mismatched_kinds( + code.s("foo", 2), + "signal 'foo' of subtype 'NATURAL' cannot be indexed", + )]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn sliced_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + signal foo : natural; +begin + foo(0 to 1) <= (0, 2); +end architecture; +", + ); + + let expected = vec![Diagnostic::mismatched_kinds( + code.s("foo", 2), + "signal 'foo' of subtype 'NATURAL' cannot be sliced", + )]; + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn sliced_names_may_be_assignment_target() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type arr1_t is array (natural range 0 to 1) of natural; + signal foo1 : arr1_t; +begin + foo1(0 to 1) <= (others => 0); + + main : process + begin + foo1(0 to 1) := (others => 0); + end process; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("foo1(0 to 1)", 2), + "signal 'foo1' may not be the target of a variable assignment", + ErrorCode::MismatchedKinds, + )], + ); +} + +#[test] +fn test_array_element_target_can_be_selected() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type rec_t is record + elem : natural; + end record; + type arr_t is array (0 to 1) of rec_t; + signal c1 : arr_t; +begin + c1(0).elem <= 1; +end architecture; + +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn test_alias_target() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type rec_t is record + elem : natural; + end record; + type arr_t is array (0 to 1) of rec_t; + signal c1 : arr_t; + alias a1 is c1; + alias a2 is c1(0); + alias a3 is a2.elem; +begin + a1(0) <= (elem => 0); + a2.elem <= 1; + a3 <= 1; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn assignment_target_all() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type rec_t is record + field: natural; + end record; + + type ptr_t is access rec_t; + + procedure proc is + variable vptr : ptr_t; + begin + -- Good + vptr.all := (field => 0); + vptr.all.field := 0; + vptr.field := 0; + + -- Bad + vptr.all := vptr; + vptr.all.all := vptr; + end procedure; + +begin +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("vptr.all := vptr").s("vptr", 2), + "variable 'vptr' of access type 'ptr_t' does not match record type 'rec_t'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("vptr.all.all").s1("vptr.all"), + "record type 'rec_t' cannot be accessed with .all", + ErrorCode::MismatchedKinds, + ), + ], + ); +} + +#[test] +fn issue_177() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package Test is + type MyArray_t is array (0 to kConst) of integer; +end Test; + +package body Test is + variable v : MyArray_t := (others => 0); + procedure Foo is + begin + -- v'range previously paniced due to MyArray_t having None as index + for i in v'range loop + v(i) := 1; + end loop; + end procedure Foo; +end package body Test;", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("kConst"), + "No declaration of 'kConst'", + ErrorCode::Unresolved, + )], + ) +} + +#[test] +pub fn assignment_mode_checking() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity bar is +end bar; + +architecture foo of bar is + signal a : integer; + shared variable b: integer; + file c: integer; + + procedure proc( + signal f_a: integer; + variable f_b: integer; + constant f_c: integer; + file f_d: integer + ) is + begin + end proc; +begin + + baz: process is + variable d: integer; + variable e: character; + begin + proc(d, c, a, b); + proc(a, b, 1 + 1, c); + proc(a, e, 1 + 1, c); + end process baz; + +end architecture foo; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("proc(d, c, a, b)").s1("d"), + "Name must denote a signal name", + ErrorCode::InterfaceModeMismatch, + ), + Diagnostic::new( + code.s1("proc(d, c, a, b)").s("c", 2), + "Name must denote a variable name", + ErrorCode::InterfaceModeMismatch, + ), + Diagnostic::new( + code.s1("proc(d, c, a, b)").s1("b"), + "Name must denote a file name", + ErrorCode::InterfaceModeMismatch, + ), + Diagnostic::new( + code.s1("proc(a, e, 1 + 1, c)").s1("e"), + "variable 'e' of type 'CHARACTER' does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + ], + ) +} + +#[test] +fn legal_file_names() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + "\ +use std.textio.all; + +package foo is + procedure tee( + file file_handle : text; + variable my_line : inout line + ); +end package foo; + +package body foo is + procedure tee( + file file_handle : text; + variable my_line : inout line + ) is + variable v_line : line; + begin + write(v_line, my_line.all); + writeline(file_handle, v_line); + end procedure tee; +end package body; + ", + ); + check_no_diagnostics(&builder.analyze()) +} diff --git a/vhdl_lang/src/analysis/tests/association_formal.rs b/vhdl_lang/src/analysis/tests/association_formal.rs new file mode 100644 index 0000000..ea48584 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/association_formal.rs @@ -0,0 +1,746 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com +use super::*; +use crate::VHDLStandard::VHDL2019; +use pretty_assertions::assert_eq; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn missing_port_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + signal sig : boolean; +begin + ent: entity work.ent_inst + port map (missing => sig); +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn missing_generic_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + signal sig : boolean; +begin + ent: entity work.ent_inst + generic map (missing => sig); +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn resolve_port_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is + port ( + theport : in boolean + ); +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + signal sig : boolean; +begin + ent: entity work.ent_inst + port map (theport => sig); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("theport", 2).end()), + Some(code.s1("theport").pos()) + ); +} + +#[test] +fn resolve_generic_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is + generic ( + thegeneric : boolean + ); +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is +begin + ent: entity work.ent_inst + generic map (thegeneric => false); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("thegeneric", 2).end()), + Some(code.s1("thegeneric").pos()) + ); +} + +#[test] +fn does_not_mixup_ports_and_generics() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is + generic ( + thegeneric : boolean + ); + port ( + theport : in boolean + ); +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + signal sig : boolean; +begin + ent: entity work.ent_inst + generic map (theport => sig) + port map (thegeneric => 0); +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("theport", 2), + "No declaration of 'theport'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("work.ent_inst"), + "No association of port 'theport' : in", + ErrorCode::Unassociated, + ) + .related(code.s1("theport"), "Defined here"), + Diagnostic::new( + code.s("thegeneric", 2), + "No declaration of 'thegeneric'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("work.ent_inst"), + "No association of generic 'thegeneric'", + ErrorCode::Unassociated, + ) + .related(code.s1("thegeneric"), "Defined here"), + ], + ); +} + +#[test] +fn resolve_port_and_surrounding_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is + port ( + theport : in integer_vector(0 to 0) + ); +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + constant const0 : natural := 0; +begin + ent: entity work.ent_inst + port map (theport(const0) => 0); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("theport", 2).end()), + Some(code.s1("theport").pos()) + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("const0", 2).end()), + Some(code.s1("const0").pos()) + ); +} + +#[test] +fn function_conversion_of_port_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + port ( + theport: out natural + ); +end entity; + +architecture a of ent is +begin +end architecture; + +entity ent2 is +end entity; + +architecture a of ent2 is + function fun1(arg : natural) return natural is + begin + return arg; + end function; + + signal sig : natural; +begin + inst: entity work.ent + port map ( + fun1(theport) => sig); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("fun1", 2).end()), + Some(code.s1("fun1").pos()) + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("theport", 2).end()), + Some(code.s1("theport").pos()) + ); +} + +#[test] +fn type_conversion_of_port_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + port ( + theport: out natural + ); +end entity; + +architecture a of ent is +begin +end architecture; + +entity ent2 is +end entity; + +architecture a of ent2 is + signal sig : real; +begin + inst: entity work.ent + port map ( + real(theport) => sig); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("theport", 2).end()), + Some(code.s1("theport").pos()) + ); +} + +#[test] +fn function_conversion_of_port_name_must_be_single_argument() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + port ( + theport: out natural + ); +end entity; + +architecture a of ent is +begin +end architecture; + +entity ent2 is +end entity; + +architecture a of ent2 is + function fun1(arg : natural; arg2: natural) return natural is + begin + return arg; + end function; + + signal sig : natural; +begin + inst: entity work.ent + port map ( + fun1(theport, 2) => sig); +end architecture; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("fun1(theport, 2)"), + "Invalid formal conversion", + ErrorCode::InvalidFormalConversion, + )], + ); +} + +#[test] +fn function_conversion_of_port_name_must_not_have_its_own_formal() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + port ( + theport: out natural + ); +end entity; + +architecture a of ent is +begin +end architecture; + +entity ent2 is +end entity; + +architecture a of ent2 is + function fun1(arg : natural) return natural is + begin + return arg; + end function; + + signal sig : natural; +begin + inst: entity work.ent + port map ( + fun1(arg => theport) => sig); +end architecture; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("fun1(arg => theport)"), + "Invalid formal conversion", + ErrorCode::InvalidFormalConversion, + )], + ); +} + +#[test] +fn conversion_of_sliced_formal() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity module is + port ( + theport : out bit_vector(0 to 5) + ); +end; + +architecture a of module is +begin +end architecture; + +entity ent is +end; + +architecture behav of ent is + signal data0 : bit_vector(0 to 5); + signal data1 : bit_vector(0 to 5); + begin + + inst0: entity work.module + port map ( + bit_vector(theport(data0'range)) => data0 + ); + + inst1: entity work.module + port map ( + bit_vector(theport(0 to 5)) => data1 + ); +end;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("theport", 2).end()), + Some(code.s1("theport").pos()) + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("theport", 3).end()), + Some(code.s1("theport").pos()) + ); +} + +#[test] +fn output_ports_may_be_left_open() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent2 is +port ( + signal inport: in natural; + signal outport: out natural ); +end entity; + +architecture a of ent2 is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + signal sig : natural; +begin + inst: entity work.ent2 + port map ( + inport => sig + ); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + // Still resolves references when missing output port + assert!(root + .search_reference(code.source(), code.s1("inport => sig").s1("sig").start()) + .is_some()) +} + +#[test] +fn does_not_stop_on_first_error() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent_inst is + port ( + prt0 : in boolean; + prt1 : in boolean + ); +end entity; + +architecture a of ent_inst is +begin +end architecture; + +entity ent is +end entity; + +architecture a of ent is + signal sig0, sig1 : boolean; +begin + ent: entity work.ent_inst + port map ( + missing => sig0, + prt1 => sig1); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("work.ent_inst"), + "No association of port 'prt0' : in", + ErrorCode::Unassociated, + ) + .related(code.s1("prt0"), "Defined here"), + ], + ); + + // Still sets the reference even if it fails + assert_eq!( + root.search_reference_pos(code.source(), code.s1("=> sig0").s1("sig0").pos().start()) + .unwrap(), + code.s1("sig0").pos() + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("=> sig1").s1("sig1").pos().start()) + .unwrap(), + code.s1("sig1").pos() + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("prt1 => ").s1("prt1").pos().start()) + .unwrap(), + code.s1("prt1").pos() + ); +} + +#[test] +fn view_array_with_explicit_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + " +package test_pkg is + type test_t is record + foo : bit; + end record; + + view vone of test_t is + foo : in; + end view; + + type test_array is array (natural range <>) of test_t; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_array_if: view (vone) of test_array(1 downto 0) + ); +end entity; + +architecture arch of test_sub_entity is +begin + my_array_if(0).foo <= '1'; +end architecture; + ", + ); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("test_array(1 downto 0)").start()) + .unwrap(), + code.s1("test_array").pos() + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("my_array_if(0).foo").end()) + .unwrap(), + code.s1("foo").pos() + ); +} + +#[test] +fn view_array_without_explicit_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + " +package test_pkg is + type test_t is record + foo : bit; + end record; + + view vone of test_t is + foo : in; + end view; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_array_if: view (vone) + ); +end entity; + +architecture arch of test_sub_entity is +begin + my_array_if(0).foo <= '1'; +end architecture; + ", + ); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("my_array_if(0).foo").end()) + .unwrap(), + code.s1("foo").pos() + ); +} + +#[test] +fn view_array_with_non_array_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + " +package test_pkg is + type test_t is record + foo : natural; + end record; + + view vone of test_t is + foo : in; + end view; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_array_if: view (vone) of bit + ); +end entity; + ", + ); + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("bit"), + "Subtype must be an array", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn view_array_with_mismatched_element_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + " +package test_pkg is + type test_t is record + foo : natural; + end record; + + view vone of test_t is + foo : in; + end view; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_array_if: view (vone) of bit_vector + ); +end entity; + ", + ); + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("bit_vector"), + "Array element type 'BIT' must match record type 'test_t' declared for the view", + ErrorCode::TypeMismatch, + )], + ); +} diff --git a/vhdl_lang/src/analysis/tests/circular_dependencies.rs b/vhdl_lang/src/analysis/tests/circular_dependencies.rs new file mode 100644 index 0000000..3556139 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/circular_dependencies.rs @@ -0,0 +1,309 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::data::error_codes::ErrorCode; + +#[test] +fn context() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " + +context ctx1 is + library libname; + context libname.ctx2; +end context; + +context ctx2 is + library libname; + context libname.ctx1; +end context; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("ctx1", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s("ctx2", 1), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn use_package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +use work.pkg2.const; + +package pkg1 is + constant const : natural := 0; +end package; + +use work.pkg1.const; + +package pkg2 is + constant const : natural := 0; +end package;", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg1", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s("pkg2", 1), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn use_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (c : boolean); + constant const : boolean := c; +end package; + +use work.pkg2.const; +package pkg1 is new work.gpkg generic map(c => false); + +use work.pkg1.const; +package pkg2 is new work.gpkg generic map(c => true); +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg1", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s1("pkg2"), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn package_instance_in_declarative_region() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +use work.pkg2.const; +package gpkg is + generic (c : boolean); + constant const : boolean := c; +end package; + +package pkg2 is + package ipkg is new work.gpkg generic map(c => false); + constant const : boolean := false; +end package; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("pkg2"), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s("gpkg", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn package_instance_in_interface() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +use work.pkg3.const; +package gpkg is + generic (c : boolean); + constant const : boolean := c; +end package; + +package pkg2 is + generic ( + package ipkg is new work.gpkg generic map(c => true) + ); + constant const : boolean := false; +end package; + +package pkg3 is new work.pkg2; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("pkg3"), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s("gpkg", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s("pkg2", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn use_package_all() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +use work.pkg2.all; + +package pkg1 is + constant const : natural := 0; +end package; + +use work.pkg1.all; + +package pkg2 is + constant const : natural := 0; +end package;", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg1", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s1("pkg2"), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn use_package_instance_all() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (g : boolean); +end package; + +use work.pkg2.all; + +package pkg1 is + constant const : natural := 0; +end package; + +use work.pkg1.all; + +package pkg2 is new work.gpkg generic map (g => true); +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg1", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s1("pkg2"), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} + +#[test] +fn use_library_all() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +use work.all; + +package pkg1 is + constant const : natural := 0; +end package; + +use work.pkg1.const; + +package pkg2 is + constant const : natural := 0; +end package;", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg1", 2), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + Diagnostic::new( + code.s("work.all", 1), + "Found circular dependency", + ErrorCode::CircularDependency, + ), + ], + ); +} diff --git a/vhdl_lang/src/analysis/tests/context_clause.rs b/vhdl_lang/src/analysis/tests/context_clause.rs new file mode 100644 index 0000000..4f4fe01 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/context_clause.rs @@ -0,0 +1,1065 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn check_library_clause_library_exists() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +library missing_lib; + +entity ent is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing_lib"), + "No such library 'missing_lib'", + ErrorCode::Unresolved, + )], + ) +} + +#[test] +fn library_clause_extends_into_secondary_units() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +-- Package will be used for testing +package usepkg is + constant const : natural := 0; +end package; + +-- This should be visible also in architectures +library libname; + +entity ent is +end entity; + +use libname.usepkg; + +architecture rtl of ent is +begin +end architecture; + +-- This should be visible also in package body +library libname; +use libname.usepkg; + +package pkg is +end package; + +use usepkg.const; + +package body pkg is +end package body; + ", + ); + + let diagnostics = builder.analyze(); + + check_no_diagnostics(&diagnostics); +} + +/// Check that context clause in secondary units work +#[test] +fn context_clause_in_secondary_units() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package usepkg is + constant const : natural := 0; +end package; + +entity ent is +end entity; + +library libname; + +architecture rtl of ent is + use libname.usepkg; +begin +end architecture; + +package pkg is +end package; + +library libname; + +package body pkg is + use libname.usepkg; +end package body; + ", + ); + + let diagnostics = builder.analyze(); + + check_no_diagnostics(&diagnostics); +} + +#[test] +fn check_library_clause_library_exists_in_context_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +context ctx is + library missing_lib; +end context; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing_lib"), + "No such library 'missing_lib'", + ErrorCode::Unresolved, + )], + ) +} + +// This test was added to fix an accidental mistake when refactoring +#[test] +fn context_clause_does_change_work_symbol_meaning() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +-- Package will be used for testing +package pkg1 is + constant const : natural := 0; +end package; + +context ctx is + library libname; + use libname.pkg1; +end context; + ", + ); + + builder.code( + "libname2", + " +package pkg2 is +end package; + +library libname; +context libname.ctx; + +use work.pkg2; + package pkg3 is +end package; + ", + ); + + let diagnostics = builder.analyze(); + + check_no_diagnostics(&diagnostics); +} + +#[test] +fn work_is_not_visible_in_context_clause() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +context ctx is + use work.pkg1; +end context; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, vec![missing(&code, "work", 1)]); +} + +#[test] +fn library_std_is_pre_defined() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +use std.textio.all; + +entity ent is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn work_library_not_necessary_hint() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +library work; + +entity ent is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("work"), + "Library clause not necessary for current working library", + ErrorCode::UnnecessaryWorkLibrary, + )], + ) +} + +#[test] +fn check_use_clause_for_missing_design_unit() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package gpkg is + generic (const : natural); +end package; + +entity ent is +end entity; + +architecture rtl of ent is +begin +end architecture; + +configuration cfg of ent is + for rtl + end for; +end configuration; + +package ipkg is new work.gpkg + generic map ( + const => 1 + ); + +library libname; + +-- Should work +use work.pkg; +use libname.pkg.all; +use libname.ent; +use libname.ipkg; +use libname.cfg; + +use work.missing_pkg; +use libname.missing_pkg.all; + + +entity dummy is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("missing_pkg", 1), + "No primary unit 'missing_pkg' within library 'libname'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("missing_pkg", 2), + "No primary unit 'missing_pkg' within library 'libname'", + ErrorCode::Unresolved, + ), + ], + ) +} + +#[test] +fn check_use_clause_for_missing_library_clause() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +use libname.pkg; + +entity dummy is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("libname", 1), + "No declaration of 'libname'", + ErrorCode::Unresolved, + )], + ) +} + +#[test] +fn nested_use_clause_missing() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + constant const : natural := 0; +end package; + +library libname; + +entity ent is +end entity; + +architecture rtl of ent is + use libname.pkg; -- Works + use libname.pkg1; -- Error +begin + process + use pkg.const; -- Works + use libname.pkg1; -- Error + begin + end process; + + blk : block + use pkg.const; -- Works + use libname.pkg1; -- Error + begin + end block; + +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("pkg1", 1), + "No primary unit 'pkg1' within library 'libname'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("pkg1", 2), + "No primary unit 'pkg1' within library 'libname'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("pkg1", 3), + "No primary unit 'pkg1' within library 'libname'", + ErrorCode::Unresolved, + ), + ], + ) +} + +#[test] +fn check_context_reference_for_missing_context() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +context ctx is +end context; + +context work.ctx; +context work.missing_ctx; + +entity dummy is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing_ctx"), + "No primary unit 'missing_ctx' within library 'libname'", + ErrorCode::Unresolved, + )], + ) +} + +#[test] +fn check_context_reference_for_non_context() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +context work.pkg; + +entity dummy is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s("pkg", 2), + "package 'pkg' does not denote a context declaration", + )], + ) +} + +#[test] +fn check_use_clause_and_context_clause_must_be_selected_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +library libname; + +context libname; +use work; +use libname; + +use work.pkg(0); +context work.ctx'range; + +entity dummy is +end entity; + ", + ); + + let diagnostics = builder.analyze(); + + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s("libname", 2), + "Context reference must be a selected name", + ), + Diagnostic::mismatched_kinds(code.s1("work"), "Use clause must be a selected name"), + Diagnostic::mismatched_kinds( + code.s("libname", 3), + "Use clause must be a selected name", + ), + Diagnostic::mismatched_kinds( + code.s1("work.pkg(0)"), + "Use clause must be a selected name", + ), + Diagnostic::mismatched_kinds( + code.s1("work.ctx'range"), + "Context reference must be a selected name", + ), + ], + ); +} + +#[test] +fn check_two_stage_use_clause_for_missing_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type enum_t is (alpha, beta); + constant const : enum_t := alpha; +end package; + +use work.pkg; +use pkg.const; +use pkg.const2; + +package pkg2 is +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("const2"), + "No declaration of 'const2' within package 'pkg'", + ErrorCode::Unresolved, + )], + ); +} +#[test] +fn check_use_clause_for_missing_name_in_package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type enum_t is (alpha, beta); + constant const : enum_t := alpha; +end package; + +use work.pkg.const; +use work.pkg.const2; + +package pkg2 is +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("const2"), + "No declaration of 'const2' within package 'pkg'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn check_use_clause_for_missing_name_in_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (constant gconst : natural); + constant const : natural := 0; +end package; + +package ipkg is new work.gpkg generic map (gconst => 0); + +use work.ipkg.const; +use work.ipkg.const2; + +-- @TODO should probably not be visible #19 +-- use work.ipkg.gconst; + +package pkg is +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + // @TODO add use instance path in error diagnostic + Diagnostic::new( + code.s1("const2"), + "No declaration of 'const2' within package instance 'ipkg'", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn error_on_use_clause_with_double_all() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + constant const1 : natural := 0; +end package; + +use work.all.all; +use work.all.foo; + +entity ent is +end entity; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s("work.all", 1), + "'.all' may not be the prefix of a selected name", + ), + Diagnostic::mismatched_kinds( + code.s("work.all", 2), + "'.all' may not be the prefix of a selected name", + ), + ], + ); +} + +#[test] +fn an_uninstantiated_package_may_not_be_prefix_of_selected_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (const : natural); +end package; + +use work.gpkg.all; + +package pkg is + use work.gpkg.const; +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s("work.gpkg", 1), + "Uninstantiated package 'gpkg' may not be the prefix of a selected name", + ), + Diagnostic::mismatched_kinds( + code.s("work.gpkg", 2), + "Uninstantiated package 'gpkg' may not be the prefix of a selected name", + ), + ], + ); +} + +#[test] +fn invalid_prefix_of_use_clause_selected_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type enum_t is (alpha, beta); + constant const : natural := 0; +end package; + +use work.pkg.enum_t.foo; +use work.pkg.const.all; + +package user is +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s("work.pkg.enum_t", 1), + "Type 'enum_t' may not be the prefix of a selected name", + ), + Diagnostic::mismatched_kinds( + code.s("work.pkg.const", 1), + "Invalid prefix for selected name", + ), + ], + ); +} + +#[test] +fn use_clause_with_selected_all_design_units() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg1 is + constant const1 : natural := 0; +end package; + +package pkg2 is + constant const2 : natural := 0; +end package; + ", + ); + + builder.code( + "libname2", + " +library libname; +use libname.all; +use pkg1.const1; +use pkg2.const2; + +entity ent is +end entity; + ", + ); + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn use_clause_with_selected_all_names() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg1 is + type enum_t is (alpha, beta); +end package; + +use work.pkg1.all; + +entity ent is +end entity; + +architecture rtl of ent is + signal foo : enum_t; +begin +end architecture; + ", + ); + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn use_all_in_package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + subtype typ is natural range 0 to 1; +end package; + +use work.pkg1.all; + +package pkg2 is + constant const : typ := 0; + constant const2 : missing := 0; +end package; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn use_all_in_primary_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (const : natural); + subtype typ is natural range 0 to 1; +end package; + +package ipkg is new work.gpkg generic map (const => 0); + +use work.ipkg.all; + +package pkg is + constant const : typ := 0; + constant const2 : missing := 0; +end package; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn use_of_interface_package_declaration() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg1 is + generic (const : natural); + subtype typ is natural range 0 to 1; +end package; + +package gpkg2 is + generic (package ipkg is new work.gpkg1 generic map (const => 1)); + use ipkg.typ; + use ipkg.missing; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing' within package instance 'ipkg'", + ErrorCode::Unresolved, + )], + ); +} +#[test] +fn use_in_local_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (const : natural); + subtype typ is natural range 0 to 1; +end package; + + +package pkg is + package ipkg is new work.gpkg generic map (const => 0); + use ipkg.typ; + + constant const : typ := 0; + constant const2 : missing := 0; +end package; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn use_all_in_local_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (const : natural); + subtype typ is natural range 0 to 1; +end package; + + +package pkg is + package ipkg is new work.gpkg generic map (const => 0); + use ipkg.all; + + constant const : typ := 0; + constant const2 : missing := 0; +end package; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn use_with_invalid_selected_name_prefix() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + subtype typ_t is natural range 0 to 1; +end package; + +use work.pkg1.typ_t.foo; + +package pkg2 is +end package; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s("work.pkg1.typ_t", 1), + "Subtype 'typ_t' may not be the prefix of a selected name", + )], + ); +} + +#[test] +fn resolves_reference_to_use_of_package() { + check_search_reference_with_name( + "pkg", + " +package pkg is +end package; + +-- Entity context clause reference +use work.pkg.all; +entity ename1 is +end entity; + +-- Architecture context clause reference +use work.pkg.all; +architecture a of ename1 is +begin +end architecture; + +-- Package context clause reference +use work.pkg.all; +package pname is +end package; + +-- Package body context clause reference +use work.pkg.all; +package body pkg is +end package body; + +-- Configuration context clause reference +use work.pkg.all; +configuration cfg of ename1 is +for rtl +end for; +end configuration; + +package genpack is + generic (constant c : natural); +end package; + +-- Package instance context clause reference +use work.pkg.all; +package ipack is new work.genpack generic map(c => 0); + +context ctx is + library libname; + -- Context declaration context clause reference + use libname.pkg.all; +end context; + +package nested is + -- Use clause in declarative region + use work.pkg.all; +end package; + +", + ); +} + +#[test] +fn resolves_context_reference() { + check_search_reference( + " +package pkg is +end package; + +context decl is + library libname; + use libname.pkg; +end context; + +context work.decl; + +package pkg2 is +end package; +", + ); +} + +#[test] +fn adds_enum_variants_implicitly() { + check_missing( + " +package pkg is + type enum_t is (alpha, beta); +end package; + +use work.pkg.enum_t; +package pkg2 is + constant c : enum_t := alpha; + constant c2 : enum_t := missing; +end package; +", + ); +} + +#[test] +fn adds_alias_enum_variants_implicitly() { + check_missing( + " +package pkg is + type enum_t is (alpha, beta); + alias alias_t is enum_t; +end package; + +use work.pkg.alias_t; +package pkg2 is + constant c : alias_t := alpha; + constant c2 : alias_t := missing; +end package; +", + ); +} + +#[test] +fn adds_alias_enum_variants_implicitly_when_using_all() { + check_missing( + " +package pkg is + type enum_t is (alpha, beta); +end package; + +package pkg2 is + alias alias_t is work.pkg.enum_t; + constant c : alias_t := alpha; +end package; + +use work.pkg2.all; +package pkg3 is + constant c : alias_t := alpha; + constant c2 : alias_t := missing; +end package; +", + ); +} diff --git a/vhdl_lang/src/analysis/tests/custom_attributes.rs b/vhdl_lang/src/analysis/tests/custom_attributes.rs new file mode 100644 index 0000000..de08878 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/custom_attributes.rs @@ -0,0 +1,329 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use crate::data::error_codes::ErrorCode; +use itertools::Itertools; + +use super::*; + +#[test] +fn signal_attribute_must_in_the_same_declarative_part() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute myattr : boolean; + + signal good, bad : natural; + attribute myattr of good : signal is true; +begin + process + attribute myattr of bad : signal is true; + begin + end process; +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("bad", 2), + "Attribute specification must be in the immediate declarative part", + ErrorCode::MisplacedAttributeSpec, + )], + ); +} + +#[test] +fn entity_attribute_must_in_the_same_declarative_part() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity myent is + attribute myattr : boolean; + attribute myattr of myent : entity is true; +end entity; + +architecture a of myent is + attribute myattr of myent : entity is true; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("myent", 4), + "Attribute specification must be in the immediate declarative part", + ErrorCode::MisplacedAttributeSpec, + )], + ); +} + +#[test] +fn interface_attribute_must_in_the_same_declarative_part() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + port (good, bad : out boolean); + attribute myattr : natural; + attribute myattr of good : signal is 1337; +end entity; + +architecture a of ent is + attribute myattr of bad : signal is 1337; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("bad", 2), + "Attribute specification must be in the immediate declarative part", + ErrorCode::MisplacedAttributeSpec, + )], + ); +} + +#[test] +fn custom_attribute_can_be_used() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is + attribute myattr : boolean; + attribute myattr of ent : entity is false; +end entity; + +architecture a of ent is + constant c0 : boolean := ent'myattr; + + signal mysig : natural; + attribute myattr of mysig : signal is false; + constant c1 : boolean := mysig'myattr; + + type mytype is (alpha, beta); + attribute myattr of mytype : type is false; + constant c2 : boolean := mytype'myattr; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn finds_references_of_custom_attributes() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + attribute myattr : boolean; + attribute myattr of ent : entity is false; +end entity; + +architecture a of ent is + constant c0 : boolean := ent'myattr; +begin +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.find_all_references_pos(&code.s1("myattr").pos()), + vec![ + code.s1("attribute myattr : boolean"), + code.s1("myattr of ent"), + code.s1("ent'myattr"), + ] + .into_iter() + .map(|c| c.s1("myattr").pos()) + .collect_vec() + ); +} + +#[test] +fn invalid_prefix_for_custom_attribute() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute myattr : boolean; + constant c1 : boolean := std'myattr; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s1("std'myattr"), + "library std may not be the prefix of a user defined attribute", + )], + ); +} + +#[test] +fn incorrect_entity_class() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute myattr : boolean; + + signal good, bad : natural; + attribute myattr of good : signal is true; + attribute myattr of bad : variable is true; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("bad", 2), + "signal 'bad' is not of class variable", + ErrorCode::MismatchedEntityClass, + )], + ); +} + +#[test] +fn subtype_entity_class() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute myattr : boolean; + + subtype good is integer range 0 to 3; + type bad is (alpha, beta); + + attribute myattr of good : subtype is true; + attribute myattr of bad : subtype is true; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("bad", 2), + "type 'bad' is not of class subtype", + ErrorCode::MismatchedEntityClass, + )], + ); +} + +#[test] +fn duplicate_attribute() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute myattr : boolean; + signal mysig : natural; + attribute myattr of mysig : signal is false; + attribute myattr of mysig : signal is true; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("mysig", 3), + "Duplicate specification of attribute 'myattr' for signal 'mysig'", + ErrorCode::Duplicate, + ) + .related(code.s("mysig", 2), "Previously specified here")], + ); +} + +#[test] +fn attributes_affect_aliased_object_and_not_alias_itself() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute myattr : boolean; + + signal mysig : natural; + alias myalias is mysig; + + attribute myattr of myalias : signal is false; + attribute myattr of mysig : signal is false; + + constant c0 : boolean := mysig'myattr; + constant c1 : boolean := myalias'myattr; +begin +end architecture; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("mysig : signal").s1("mysig"), + "Duplicate specification of attribute 'myattr' for signal 'mysig'", + ErrorCode::Duplicate, + ) + .related( + code.s1("myalias : signal").s1("myalias"), + "Previously specified here", + )], + ); +} diff --git a/vhdl_lang/src/analysis/tests/declarations.rs b/vhdl_lang/src/analysis/tests/declarations.rs new file mode 100644 index 0000000..fbca954 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/declarations.rs @@ -0,0 +1,102 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::tests::{check_diagnostics, LibraryBuilder}; +use crate::data::error_codes::ErrorCode; +use crate::Diagnostic; + +#[test] +pub fn declaration_not_allowed_everywhere() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity ent is +end entity; + +architecture arch of ent is + +function my_func return natural is + signal x : bit; +begin + +end my_func; +begin + + my_block : block + variable y: natural; + begin + end block my_block; + +end architecture; + ", + ); + check_diagnostics( + builder.analyze(), + vec![ + Diagnostic::new( + code.s1("signal x : bit;"), + "signal declaration not allowed here", + ErrorCode::DeclarationNotAllowed, + ), + Diagnostic::new( + code.s1("variable y: natural;"), + "variable declaration not allowed here", + ErrorCode::DeclarationNotAllowed, + ), + ], + ) +} + +// Issue #242 +#[test] +pub fn attribute_with_wrong_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity test is + attribute some_attr : string; + attribute some_attr of test : signal is \"some value\"; +end entity test; + ", + ); + let (_, diag) = builder.get_analyzed_root(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("test : signal").s1("test"), + "entity 'test' is not of class signal", + ErrorCode::MismatchedEntityClass, + )], + ) +} + +#[test] +pub fn attribute_sees_through_aliases() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity test is + port ( + clk: in bit + ); + alias aliased_clk is clk; + attribute some_attr : string; + attribute some_attr of aliased_clk : entity is \"some value\"; +end entity test; + ", + ); + let (_, diag) = builder.get_analyzed_root(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("aliased_clk : entity").s1("aliased_clk"), + "port 'clk' : in is not of class entity", + ErrorCode::MismatchedEntityClass, + )], + ) +} diff --git a/vhdl_lang/src/analysis/tests/deferred_constant.rs b/vhdl_lang/src/analysis/tests/deferred_constant.rs new file mode 100644 index 0000000..f97bc79 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/deferred_constant.rs @@ -0,0 +1,158 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn allows_deferred_constant() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is +constant a : natural; +end package; + +package body pkg is +constant a : natural := 0; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn forbid_deferred_constant_after_constant() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural := 0; +constant a1 : natural; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_deferred_constant_outside_of_package_declaration() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is +constant a1 : natural; +constant a1 : natural := 0; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("a1"), + "Deferred constants are only allowed in package declarations (not body)", + ErrorCode::IllegalDeferredConstant, + )], + ); +} + +#[test] +fn forbid_full_declaration_of_deferred_constant_outside_of_package_body() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural; +constant a1 : natural := 0; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("a1", 1), + "Deferred constant 'a1' lacks corresponding full constant declaration in package body", + ErrorCode::MissingDeferredDeclaration + ),Diagnostic::new( + code.s("a1", 2), + "Full declaration of deferred constant is only allowed in a package body", + ErrorCode::IllegalDeferredConstant + )], + ); +} + +#[test] +fn error_on_missing_full_constant_declaration() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg_no_body is +constant a1 : natural; +end package; + +package pkg is +constant b1 : natural; +end package; + +package body pkg is +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("a1"), + "Deferred constant 'a1' lacks corresponding full constant declaration in package body", + ErrorCode::MissingDeferredDeclaration + ), + Diagnostic::new( + code.s1("b1"), + "Deferred constant 'b1' lacks corresponding full constant declaration in package body", + ErrorCode::MissingDeferredDeclaration + ), + ], + ); +} + +#[test] +fn forbid_multiple_constant_after_deferred_constant() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural; +end package; + +package body pkg is +constant a1 : natural := 0; +constant a1 : natural := 0; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, vec![duplicate(&code, "a1", 2, 3)]); +} diff --git a/vhdl_lang/src/analysis/tests/hierarchy.rs b/vhdl_lang/src/analysis/tests/hierarchy.rs new file mode 100644 index 0000000..d98973e --- /dev/null +++ b/vhdl_lang/src/analysis/tests/hierarchy.rs @@ -0,0 +1,426 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::data::error_codes::ErrorCode; +use crate::EntHierarchy; +use crate::Source; +use pretty_assertions::assert_eq; + +#[test] +fn entity_architecture() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is + generic ( + g0 : natural + ); + port ( + p0 : bit + ); +end entity; + +architecture a of ent is + signal s0 : natural; +begin + block + begin + process + variable v0 : natural; + begin + loop0: loop + end loop; + + if false then + end if; + end process; + end block; +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + assert_eq!( + get_hierarchy(&root, "libname", code.source()), + vec![ + nested("ent", vec![single("g0"), single("p0"),]), + nested( + "a", + vec![ + single("s0"), + nested( + "block", + vec![nested( + "process", + vec![single("v0"), single("loop0"), single("if statement")] + )] + ), + ] + ) + ] + ); +} + +#[test] +fn package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + function fun0(arg : natural) return natural; +end package; + + +package body pkg is + function fun0(arg : natural) return natural is + variable v0 : natural; + begin + end function; +end package body; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + get_hierarchy(&root, "libname", code.source()), + vec![ + nested("pkg", vec![nested("fun0", vec![single("arg"),]),]), + nested( + "pkg", + vec![nested("fun0", vec![single("arg"), single("v0")]),] + ) + ] + ); +} + +#[test] +fn generic_package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + generic ( + type type_t; + value: type_t + ); + + constant c0 : type_t := value; + function fun0(arg: type_t) return boolean; +end package; + +package body pkg is + function fun0(arg: type_t) return boolean is + begin + return arg = value; + end function; +end package body; + +package ipkg is new work.pkg generic map(type_t => integer, value => 0); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + assert_eq!( + get_hierarchy(&root, "libname", code.source()), + vec![ + nested( + "pkg", + vec![ + single("type_t"), + single("value"), + single("c0"), + nested("fun0", vec![single("arg"),]), + ] + ), + nested("pkg", vec![nested("fun0", vec![single("arg")]),]), + single("ipkg"), + ] + ); + + let ipkg = root + .search_reference(code.source(), code.s1("ipkg").start()) + .unwrap(); + + let instances: Vec<_> = if let AnyEntKind::Design(Design::PackageInstance(region)) = ipkg.kind() + { + let mut symbols: Vec<_> = region.immediates().collect(); + symbols.sort_by_key(|ent| ent.decl_pos()); + symbols.into_iter().map(|ent| ent.path_name()).collect() + } else { + panic!("Expected instantiated package"); + }; + + assert_eq!(instances, vec!["libname.ipkg.c0", "libname.ipkg.fun0"]); +} + +#[test] +fn public_symbols() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is + type type_t is (alpha, beta); + constant const0 : type_t := alpha; + function fun0(arg: type_t) return boolean; + function \"+\"(arg: type_t) return boolean; + + type prot_t is protected + procedure proc0(arg: type_t); + end protected; +end package; + +package body pkg is + type prot_t is protected body + procedure proc0(arg: type_t) is + begin + end; + end protected body; +end package body; + +entity ent is + generic ( + g0 : natural + ); + port ( + p0 : natural + ); +end entity; + +architecture a of ent is + signal not_public : bit; +begin + main: process + begin + end process; +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + let mut symbols: Vec<_> = root + .public_symbols() + .filter(|ent| ent.library_name() == Some(&root.symbol_utf8("libname"))) + .collect(); + symbols.sort_by_key(|ent| ent.decl_pos()); + + assert_eq!( + symbols + .into_iter() + .map(|ent| ent.path_name()) + .collect::>(), + vec![ + "libname", + "libname.pkg", + "libname.pkg.type_t", + "libname.pkg.type_t.alpha", + "libname.pkg.type_t.beta", + "libname.pkg.const0", + "libname.pkg.fun0", + "libname.pkg.\"+\"", + "libname.pkg.prot_t", + "libname.pkg.prot_t.proc0", + "libname.pkg", + "libname.ent", + "libname.ent.g0", + "libname.ent.p0", + "libname.ent.a", + ] + ); +} + +#[derive(PartialEq, Debug)] +struct NameHierarchy { + name: String, + children: Vec, +} + +/// For compact test data creation +fn nested(name: &str, children: Vec) -> NameHierarchy { + NameHierarchy { + name: name.to_owned(), + children, + } +} +/// For compact test data creation +fn single(name: &str) -> NameHierarchy { + NameHierarchy { + name: name.to_owned(), + children: Vec::new(), + } +} + +impl<'a> From> for NameHierarchy { + fn from(ent: EntHierarchy<'_>) -> Self { + NameHierarchy { + name: if matches!(ent.ent.designator(), Designator::Anonymous(_)) { + ent.ent.kind().describe().to_string() + } else { + ent.ent.designator().to_string() + }, + children: ent.children.into_iter().map(NameHierarchy::from).collect(), + } + } +} + +fn get_hierarchy(root: &DesignRoot, libname: &str, source: &Source) -> Vec { + root.document_symbols(&root.symbol_utf8(libname), source) + .into_iter() + .map(|item| item.0) + .map(NameHierarchy::from) + .collect() +} + +#[test] +fn find_implementation_of_entity_vs_component() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent0 is +end entity; + +architecture a of ent0 is +begin +end architecture; + +entity ent1 is +end entity; + +architecture a of ent1 is + component ent0 is + end component; +begin + inst: ent0; +end architecture; + + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let ent = root + .search_reference(code.source(), code.s1("ent0").start()) + .unwrap(); + let arch = root + .search_reference(code.source(), code.s1("a ").start()) + .unwrap(); + let comp = root + .search_reference(code.source(), code.sa("component ", "ent0").start()) + .unwrap(); + + assert_eq!(root.find_implementation(ent), vec![arch, comp]); + assert_eq!(root.find_implementation(comp), vec![ent]); +} + +#[test] +fn exit_and_next_outside_of_loop() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin + process + begin + exit; + next; + + loop + exit; + end loop; + + loop + next; + end loop; + end process; +end architecture; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("exit;"), + "Exit can only be used inside a loop", + ErrorCode::ExitOutsideLoop, + ), + Diagnostic::new( + code.s1("next;"), + "Next can only be used inside a loop", + ErrorCode::NextOutsideLoop, + ), + ], + ); +} + +#[test] +fn exit_and_next_label_outside_of_loop() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin + main: process + begin + good0: loop + good1: loop + exit good0; + end loop; + end loop; + + bad0: loop + exit; + end loop; + + l1: loop + exit bad0; + end loop; + + l0: loop + next bad0; + end loop; + end process; +end architecture; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.sa("exit ", "bad0"), + "Cannot be used outside of loop 'bad0'", + ErrorCode::InvalidLoopLabel, + ), + Diagnostic::new( + code.sa("next ", "bad0"), + "Cannot be used outside of loop 'bad0'", + ErrorCode::InvalidLoopLabel, + ), + ], + ); +} diff --git a/vhdl_lang/src/analysis/tests/homographs.rs b/vhdl_lang/src/analysis/tests/homographs.rs new file mode 100644 index 0000000..57c2509 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/homographs.rs @@ -0,0 +1,1069 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::data::error_codes::ErrorCode; + +#[test] +fn allows_unique_names() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is +constant a : natural := 0; +constant b : natural := 0; +constant c : natural := 0; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn allow_homographs_in_separate_blocks() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity A is +end A; + +architecture arch of A is + component Z is end component; +begin + + First : block + begin + Z_inst : Z; + end block; + + Second : block + begin + Z_inst : Z; + end block; +end arch; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn forbid_homographs() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural := 0; +constant a : natural := 0; +constant a1 : natural := 0; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_subprogram_bodies() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is +procedure proc(a1, a, a1 : natural) is +constant b1 : natural := 0; +constant b : natural := 0; +constant b1 : natural := 0; + +procedure nested_proc(c1, c, c1 : natural) is + constant d1 : natural := 0; + constant d : natural := 0; + constant d1 : natural := 0; +begin +end; + +begin +end; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1", "c1", "d1"])); +} + +#[test] +fn forbid_homographs_in_component_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +component comp is +generic ( + a1 : natural; + a : natural; + a1 : natural; + c1 : natural +); +port ( + b1 : natural; + b : natural; + b1 : natural; + c1 : natural +); +end component; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1", "c1"])); +} + +#[test] +fn forbid_homographs_in_record_type_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +type rec_t is record +a1 : natural; +a : natural; +a1 : natural; +end record; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_proteced_type_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +type prot_t is protected +procedure proc(a1, a, a1 : natural); +end protected; + +type prot_t is protected body +constant b1 : natural := 0; +constant b : natural := 0; +constant b1 : natural := 0; +end protected body; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_in_subprogram_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +procedure proc(a1, a, a1 : natural); +function fun(b1, a, b1 : natural) return natural; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_in_subprogram_iface_list_and_body() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + procedure proc(a1, a : natural) is + constant a1 : natural := 0; + begin + end; + + function fun(b1, a : natural) return natural is + constant b1 : natural := 0; + begin + return 0; + end; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_in_block() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +begin +blk : block +constant a1 : natural := 0; +constant a : natural := 0; +constant a1 : natural := 0; +begin +process + constant b1 : natural := 0; + constant b : natural := 0; + constant b1 : natural := 0; +begin +end process; +end block; +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_in_process() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +begin +process +constant a1 : natural := 0; +constant a : natural := 0; +constant a1 : natural := 0; +begin +end process; +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_for_generate() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +begin +gen_for: for i in 0 to 3 generate +constant a1 : natural := 0; +constant a : natural := 0; +constant a1 : natural := 0; +begin +process + constant b1 : natural := 0; + constant b : natural := 0; + constant b1 : natural := 0; +begin +end process; +end generate; +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_if_generate() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +begin +gen_if: if true generate +constant a1 : natural := 0; +constant a : natural := 0; +constant a1 : natural := 0; +begin + +prcss : process + constant b1 : natural := 0; + constant b : natural := 0; + constant b1 : natural := 0; +begin +end process; + +else generate +constant c1 : natural := 0; +constant c: natural := 0; +constant c1 : natural := 0; +begin +prcss : process + constant d1 : natural := 0; + constant d : natural := 0; + constant d1 : natural := 0; +begin +end process; +end generate; +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1", "c1", "d1"])); +} + +#[test] +fn forbid_homographs_with_for_generate_loop_var() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +begin + gen_for: for a1 in 0 to 3 generate + constant a1 : natural := 0; + constant a : natural := 0; + begin + end generate; +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_case_generate() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +begin +gen_case: case 0 generate +when others => + constant a1 : natural := 0; + constant a : natural := 0; + constant a1 : natural := 0; +begin + process + constant b1 : natural := 0; + constant b : natural := 0; + constant b1 : natural := 0; + begin + end process; +end generate; +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_in_entity_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +generic ( +a1 : natural; +a : natural; +a1 : natural +); +port ( +b1 : natural; +b : natural; +b1 : natural +); +constant c1 : natural := 0; +constant c : natural := 0; +constant c1 : natural := 0; +begin + +blk : block +constant d1 : natural := 0; +constant d : natural := 0; +constant d1 : natural := 0; +begin + +end block; + +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1", "c1", "d1"])); +} + +#[test] +fn forbid_homographs_in_architecture_bodies() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture arch of ent is +constant a1 : natural := 0; +constant a : natural := 0; +constant a1 : natural := 0; +begin + +blk : block +constant b1 : natural := 0; +constant b : natural := 0; +constant b1 : natural := 0; +begin +end block; + +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn forbid_homographs_of_type_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural := 0; +type a1 is (foo, bar); +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_of_component_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural := 0; +component a1 is +port (clk : bit); +end component; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_of_file_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant a1 : natural := 0; +file a1 : std.textio.text; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_package_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is +generic (foo : natural); +end package; + +package pkg is +package a1 is new work.gpkg generic map (foo => 0); +package a1 is new work.gpkg generic map (foo => 0); +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_attribute_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +attribute a1 : string; +attribute a1 : string; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_alias_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + +constant c0 : natural := 0; +constant c1 : natural := 0; + +alias a1 is c0; +alias a1 is c1; + +function f1 return natural; +function f2 return boolean; + +-- Legal since subprograms are overloaded +alias b1 is f1[return natural]; +alias b1 is f2[return boolean]; +end package pkg; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_for_overloaded_vs_non_overloaded() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant foo : natural := 0; +function bar return boolean; + +alias a1 is foo; +alias a1 is bar[return boolean]; + +function b1 return natural; +constant b1 : natural := 0; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1", "b1"])); +} + +#[test] +fn enum_literals_may_overload() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is +type enum_t is (a1, b1); + +-- Ok since enumerations may overload +type enum2_t is (a1, b1); +end package; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn forbid_homograph_to_enum_literals() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +type enum_t is (a1, b1); +constant a1 : natural := 0; +function b1 return natural; +end package pkg; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn homograph_of_enum_literal_declared_by_alias() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type enum_t is (alpha, beta); +end package; + +package pkg2 is + alias alias_t is work.pkg.enum_t; + constant alpha : natural := 0; +end package; +", + ); + + let diagnostics = builder.analyze(); + let error = Diagnostic::new( + code.s("alpha", 2), + "Duplicate declaration of 'alpha'", + ErrorCode::Duplicate, + ) + .related(code.s("alias_t", 1), "Previously defined here"); + check_diagnostics(diagnostics, vec![error]); +} + +#[test] +fn forbid_homographs_in_interface_file_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +procedure proc(file a1, a, a1 : std.textio.text); +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_interface_type_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +generic ( +type a1; +type a1 +); +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_interface_package_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is +generic (const : natural); +end package; + +entity ent is +generic ( +package a1 is new work.gpkg generic map (const => 0); +package a1 is new work.gpkg generic map (const => 0) +); +end entity; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["a1"])); +} + +#[test] +fn forbid_homographs_in_entity_extended_declarative_regions() { + let mut builder = LibraryBuilder::new(); + let ent = builder.code( + "libname", + " +entity ent is +generic ( +constant g1 : natural; +constant g2 : natural; +constant g3 : natural; +constant g4 : natural +); +port ( +signal g1 : natural; +signal p1 : natural; +signal p2 : natural; +signal p3 : natural +); +constant g2 : natural := 0; +constant p1 : natural := 0; +constant e1 : natural := 0; +constant e2 : natural := 0; +end entity;", + ); + + let arch1 = builder.code( + "libname", + " +architecture rtl of ent is +constant g3 : natural := 0; +constant p2 : natural := 0; +constant e1 : natural := 0; +constant a1 : natural := 0; +begin +end architecture;", + ); + + let arch2 = builder.code( + "libname", + " +architecture rtl2 of ent is +constant a1 : natural := 0; +constant e2 : natural := 0; +begin +end architecture; +", + ); + + let diagnostics = builder.analyze(); + let mut expected = duplicates(&ent, &["g1", "g2", "p1"]); + expected.append(&mut duplicate_in_two_files( + &ent, + &arch1, + &["g3", "p2", "e1"], + )); + expected.append(&mut duplicate_in_two_files(&ent, &arch2, &["e2"])); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn forbid_homographs_in_package_extended_declarative_regions() { + let mut builder = LibraryBuilder::new(); + let pkg = builder.code( + "libname", + " +package pkg is +generic ( +constant g1 : natural; +constant g2 : natural +); +constant g1 : natural := 0; +end package;", + ); + + let body = builder.code( + "libname", + " +package body pkg is +constant g1 : natural := 0; +constant g2 : natural := 0; +constant p1 : natural := 0; +end package body;", + ); + + let diagnostics = builder.analyze(); + let mut expected = duplicates(&pkg, &["g1"]); + expected.append(&mut duplicate_in_two_files(&pkg, &body, &["g1", "g2"])); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn forbid_homographs_of_physical_type_units() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type phys_t is range 0 to 10 + units + bangs; + bugs = 10 bangs; + end units; + + type phys2_t is range 0 to 10 + units + bangs; + bugs = 10 bangs; + end units; + + constant bangs : natural := 0; + constant bugs : natural := 0; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + // Primary unit + duplicate(&code, "bangs", 1, 3), + duplicate(&code, "bangs", 1, 5), + // Secondary units + duplicate(&code, "bugs", 1, 2), + duplicate(&code, "bugs", 1, 3), + Diagnostic::new( + code.s("10 bangs", 2).s1("bangs"), + "Physical unit of type 'phys_t' does not match physical type 'phys2_t'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn concurrent_labels_are_homographs_of_outer_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + signal lab1 : natural; + signal lab2 : natural; +begin + lab1 : process is + constant lab1 : natural := 0; -- Allow shadow + begin + end process; + + lab2 : block is + constant lab2 : natural := 0; -- Allow shadow + begin + end block; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + duplicate(&code, "lab1", 2, 1), + duplicate(&code, "lab2", 2, 1), + ], + ); +} + +#[test] +fn alternate_generate_labels_are_homographs_of_inner_declarations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin + gen: if alt1: true generate + constant alt1 : boolean := true; + constant alt2 : boolean := true; + constant alt3 : boolean := true; + begin + elsif alt2: false generate + constant alt1 : boolean := true; + constant alt2 : boolean := true; + constant alt3 : boolean := true; + begin + else alt3: generate + constant alt1 : boolean := true; + constant alt2 : boolean := true; + constant alt3 : boolean := true; + begin + end generate; +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + duplicate(&code, "alt1", 1, 2), + duplicate(&code, "alt2", 2, 3), + duplicate(&code, "alt3", 3, 4), + ], + ); +} + +#[test] +fn overloaded_with_identical_signatures_are_homographs() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + function name1 return natural; + function name1 return natural; +end package; + +package body pkg is + function name2(arg: string) return boolean is + begin + return false; + end; + + function name2(arg: string) return boolean is + begin + return false; + end; + +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("name1", 2), + "Duplicate declaration of 'name1' with signature [return NATURAL]", + ErrorCode::Duplicate, + ) + .related(code.s("name1", 1), "Previously defined here"), + Diagnostic::new( + code.s("name2", 2), + "Duplicate declaration of 'name2' with signature [STRING return BOOLEAN]", + ErrorCode::Duplicate, + ) + .related(code.s("name2", 1), "Previously defined here"), + ], + ); +} + +#[test] +fn overloaded_declaration_is_not_homograph_with_definition() { + check_code_with_no_diagnostics( + " +package pkg is + function name1 return natural; +end package; + +package body pkg is + function name1 return natural is + begin + end; +end package body; +", + ); +} + +#[test] +fn overloaded_alias_with_identical_signatures_are_homographs() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + function f1 return natural is + begin + return 0; + end function; + + function f2 return natural is + begin + return 0; + end function; + + -- Not ok since f1 and f2 are different functions with the same signature + alias homo1 is f1[return natural]; + alias homo1 is f2[return natural]; +begin +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("homo1", 2), + "Duplicate declaration of 'homo1' with signature [return NATURAL]", + ErrorCode::Duplicate, + ) + .related(code.s("homo1", 1), "Previously defined here")], + ); +} + +// This was a bug at one point +#[test] +fn same_label_in_different_process_is_not_homograph() { + check_code_with_no_diagnostics( + " +entity ent is +end entity; + +architecture a of ent is +begin + process + begin + l0 : loop + end loop; + wait; + end process; + + process + begin + l0 : loop + end loop; + wait; + end process; +end architecture; + ", + ); +} diff --git a/vhdl_lang/src/analysis/tests/implicit.rs b/vhdl_lang/src/analysis/tests/implicit.rs new file mode 100644 index 0000000..d008297 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/implicit.rs @@ -0,0 +1,285 @@ +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn adds_to_string_for_standard_types() { + check_code_with_no_diagnostics( + " +package pkg is + alias alias1 is to_string[integer return string]; + alias alias2 is minimum[integer, integer return integer]; + alias alias3 is maximum[integer, integer return integer]; +end package; +", + ); +} + +// procedure FILE_OPEN (file F: FT; External_Name: in STRING; Open_Kind: in FILE_OPEN_KIND := READ_MODE); +// procedure FILE_OPEN (Status: out FILE_OPEN_STATUS; file F: FT; External_Name: in STRING; Open_Kind: in FILE_OPEN_KIND := READ_MODE); +// procedure FILE_CLOSE (file F: FT); +// procedure READ (file F: FT; VALUE: out TM); +// procedure WRITE (file F: FT; VALUE: in TM); +// procedure FLUSH (file F: FT); +// function ENDFILE (file F: FT) return BOOLEAN +#[test] +fn adds_file_subprograms_implicitly() { + check_code_with_no_diagnostics( + " +package pkg is +end package; + +package body pkg is + type binary_file_t is file of character; + + procedure proc is + file f : binary_file_t; + variable char : character; + begin + file_open(f, \"foo.txt\"); + assert not endfile(f); + write(f, 'c'); + flush(f); + read(f, char); + file_close(f); + end procedure; +end package body; +", + ); +} + +#[test] +fn adds_to_string_for_integer_types() { + check_code_with_no_diagnostics( + " +package pkg is + type type_t is range 0 to 1; + alias my_to_string is to_string[type_t return string]; +end package; +", + ); +} + +#[test] +fn adds_to_string_for_array_types() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "lib", + " +package pkg is + type type_t is array (natural range 0 to 1) of bit; + alias my_to_string is to_string[type_t return string]; + + type bad_t is array (natural range 0 to 1) of integer; + alias bad_to_string is to_string[bad_t return string]; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + without_related(&diagnostics), + vec![Diagnostic::new( + code.sa("bad_to_string is ", "to_string"), + "Could not find declaration of 'to_string' with given signature", + ErrorCode::NoOverloadedWithSignature, + )], + ) +} + +#[test] +fn adds_to_string_for_enum_types() { + check_code_with_no_diagnostics( + " +package pkg is + type enum_t is (alpha, beta); + alias my_to_string is to_string[enum_t return string]; +end package; + ", + ); +} + +#[test] +fn no_error_for_duplicate_alias_of_implicit() { + check_code_with_no_diagnostics( + " +package pkg is + type type_t is array (natural range 0 to 1) of integer; + alias alias_t is type_t; + -- Should result in no error for duplication definiton of for example TO_STRING +end package; +", + ); +} + +#[test] +fn deallocate_is_defined_for_access_type() { + check_code_with_no_diagnostics( + " +package pkg is + type arr_t is array (natural range <>) of character; + type ptr_t is access arr_t; +end package; + +package body pkg is + procedure theproc is + variable theptr: ptr_t; + begin + deallocate(theptr); + end procedure; +end package body; +", + ); +} + +#[test] +fn enum_implicit_function_is_added_on_use() { + check_code_with_no_diagnostics( + " +package pkg1 is + type enum_t is (alpha, beta); +end package; + +use work.pkg1.enum_t; +package pkg is + alias my_to_string is to_string[enum_t return string]; +end package; +", + ); +} + +#[test] +fn find_all_references_does_not_include_implicits() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "lib", + " +package pkg is +type enum_t is (alpha, beta); +alias my_to_string is to_string[enum_t return string]; +end package; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq_unordered( + &root.find_all_references_pos(&code.s1("enum_t").pos()), + &[code.s("enum_t", 1).pos(), code.s("enum_t", 2).pos()], + ); +} + +#[test] +fn goto_references_for_implicit() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "lib", + " +package pkg is +type enum_t is (alpha, beta); +alias thealias is to_string[enum_t return string]; +end package; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let to_string = code.s1("to_string"); + assert_eq!( + root.search_reference_pos(to_string.source(), to_string.start()), + Some(code.s1("enum_t").pos()) + ); +} + +#[test] +fn hover_for_implicit() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "lib", + " +package pkg is +type enum_t is (alpha, beta); +alias thealias is to_string[enum_t return string]; +end package; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let to_string = code.s1("to_string"); + assert_eq!( + root.format_declaration( + root.search_reference(to_string.source(), to_string.start()) + .unwrap() + ), + Some( + "\ +-- function TO_STRING[enum_t return STRING] + +-- Implicitly defined by: +type enum_t is (alpha, beta); +" + .to_owned() + ) + ); +} + +#[test] +fn implicit_functions_on_physical_type() { + check_code_with_no_diagnostics( + " +package pkg is + type time_t is range 0 to 1 + units + small; + big = 1000 small; + end units; + + constant c0 : time_t := 10 small; + constant good1 : time_t := - c0; + constant good2 : time_t := + c0; + constant good3 : time_t := abs c0; + constant good4 : time_t := c0 + c0; + constant good5 : time_t := c0 - c0; + constant good6 : time_t := minimum(c0, c0); + constant good7 : time_t := maximum(c0, c0); +end package; +", + ); +} + +#[test] +fn implicit_functions_on_integer_type() { + check_code_with_no_diagnostics( + " +package pkg is + type type_t is range 0 to 1; + + constant c0 : type_t := 10; + constant good1 : type_t := - c0; + constant good2 : type_t := + c0; + constant good3 : type_t := abs c0; + constant good4 : type_t := c0 + c0; + constant good5 : type_t := c0 - c0; + constant good6 : type_t := minimum(c0, c0); + constant good7 : type_t := maximum(c0, c0); + constant good8 : string := to_string(c0); +end package; +", + ); +} + +#[test] +fn implicit_real_vs_integer_functions() { + check_code_with_no_diagnostics( + " +package pkg is + constant x : real := real(2.0 / 2); + constant y : real := real(2.0 * 2); + constant z : real := real(2 * 2.0); +end package; +", + ); +} diff --git a/vhdl_lang/src/analysis/tests/incomplete_type.rs b/vhdl_lang/src/analysis/tests/incomplete_type.rs new file mode 100644 index 0000000..49b28a0 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/incomplete_type.rs @@ -0,0 +1,183 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +// @TODO +// 5.4.2 Incomplete type declarations +// Prior to the end of the corresponding full type declaration, the only allowed use of a name that denotes a type +// declared by an incomplete type declaration is as the type mark in the subtype indication of an access type +// definition; no constraints are allowed in this subtype indication. + +use super::*; +use crate::data::error_codes::ErrorCode; +use crate::data::SrcPos; + +#[test] +fn allows_incomplete_type_definition() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is + type rec_t; + type rec_t is record + end record; + + type enum_t; + type enum_t is (alpha, beta); +end package; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn error_on_duplicate_incomplete_type_definition() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +type rec_t; +type rec_t; +type rec_t is record +end record; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["rec_t"])); +} + +#[test] +fn error_on_missing_full_type_definition_for_incomplete() { + let mut builder = LibraryBuilder::new(); + let code_pkg = builder.code( + "libname", + " +package pkg is +type rec_t; +end package; + +package body pkg is +-- Must appear in the same immediate declarative region +type rec_t is record +end record; +end package body; +", + ); + + let code_ent = builder.code( + "libname", + " +entity ent is +end entity; + +architecture rtl of ent is +type rec_t; +begin +blk : block +-- Must appear in the same immediate declarative region +type rec_t is record +end record; +begin +end block; +end architecture; +", + ); + + let code_pkg2 = builder.code( + "libname", + " +-- To check that no duplicate errors are made when closing the immediate and extended regions +package pkg2 is +type rec_t; +end package; + +package body pkg2 is +end package body; +", + ); + + let mut expected_diagnostics = Vec::new(); + for code in [&code_pkg, &code_ent, &code_pkg2].iter() { + expected_diagnostics.push(missing_full_error(&code.s1("rec_t"))); + } + + expected_diagnostics.push(duplicate(&code_pkg, "rec_t", 1, 2)); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected_diagnostics); +} + +#[test] +fn incomplete_type_references_point_to_full_definition() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type rec_t; + type access_t is access rec_t; + type rec_t is record + node: access_t; + end record; + + procedure proc(val : rec_t); +end package; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + // Reference from incomplete goes to full + for i in 1..=4 { + assert_eq!( + root.search_reference_pos(code.source(), code.s("rec_t", i).start()), + Some(code.s("rec_t", 3).pos()), + "{i}" + ); + } + + let references: Vec<_> = (1..=4).map(|idx| code.s("rec_t", idx).pos()).collect(); + assert_eq!( + root.find_all_references_pos(&code.s("rec_t", 3).pos()), + references + ); +} + +#[test] +fn error_on_missing_full_type_definition_for_incomplete_still_defines_the_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type rec_t; + type acces_t is access rec_t; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, vec![missing_full_error(&code.s1("rec_t"))]); +} + +fn missing_full_error(pos: &impl AsRef) -> Diagnostic { + let mut error = Diagnostic::new( + pos, + "Missing full type declaration of incomplete type 'rec_t'", + ErrorCode::MissingFullTypeDeclaration, + ); + error.add_related( + pos, + "The full type declaration shall occur immediately within the same declarative part", + ); + error +} diff --git a/vhdl_lang/src/analysis/tests/incremental_analysis.rs b/vhdl_lang/src/analysis/tests/incremental_analysis.rs new file mode 100644 index 0000000..dde6370 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/incremental_analysis.rs @@ -0,0 +1,357 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::analysis::DesignRoot; +use crate::ast::search::*; +use crate::data::SrcPos; +use crate::named_entity::{EntityId, Reference}; +use crate::syntax::TokenAccess; +use fnv::FnvHashSet; +use pretty_assertions::assert_eq; + +#[test] +fn incremental_analysis_of_use_within_package() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is + constant const : natural := 0; +end package; +", + ); + + builder.code( + "libname", + " +use work.pkg.const; + +package pkg2 is +end package; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +#[test] +fn incremental_analysis_of_package_use() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is + constant const : natural := 0; +end package; +", + ); + + builder.code( + "libname", + " +use work.pkg; + +package pkg2 is +end package; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +#[test] +fn incremental_analysis_of_entity_architecture() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; +", + ); + + builder.code( + "libname", + " +architecture a of ent is +begin +end architecture; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +#[test] +fn incremental_analysis_of_package_and_body() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is +end package; +", + ); + + builder.code( + "libname", + " +package body pkg is +end package body; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +#[test] +fn incremental_analysis_of_entity_instance() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin +end architecture; +", + ); + + builder.code( + "libname", + " +entity ent2 is +end entity; + +architecture a of ent2 is +begin + inst: entity work.ent; +end architecture; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +#[test] +fn incremental_analysis_of_configuration_instance() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin +end architecture; +", + ); + + builder.code( + "libname", + " +configuration cfg of ent is +for rtl +end for; +end configuration; +", + ); + + builder.code( + "libname", + " +entity ent2 is +end entity; + +architecture a of ent2 is +begin + inst : configuration work.cfg; +end architecture; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +#[test] +fn incremental_analysis_library_all_collision() { + let mut builder = LibraryBuilder::new(); + let lib1 = builder.code( + "libname1", + " +package pkg is +end package; +", + ); + + let lib2 = builder.code( + "libname2", + " +package pkg is + constant const : natural := 0; +end package; +", + ); + + let code = builder.code( + "libname3", + " + +library libname1; +use libname1.all; + +library libname2; +use libname2.all; + +use pkg.const; + +package pkg is +end package; +", + ); + + use super::visibility::hidden_error; + check_incremental_analysis( + builder, + vec![hidden_error( + &code, + "pkg", + 1, + &[ + (&code, "libname1.all", 1, false), + (&lib1, "pkg", 1, true), + (&code, "libname2.all", 1, false), + (&lib2, "pkg", 1, true), + ], + )], + ); +} + +#[test] +fn incremental_analysis_of_package_and_body_with_deferred_constant() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is + constant deferred : natural; +end package; +", + ); + + builder.code( + "libname", + " +package body pkg is + constant deferred : natural := 0; +end package body; +", + ); + + check_incremental_analysis(builder, vec![]); +} + +fn check_incremental_analysis(builder: LibraryBuilder, expected_diagnostics: Vec) { + let symbols = builder.symbols(); + let codes = builder.take_code(); + + // Generate all combinations of removing and adding source + for i in 0..codes.len() { + let mut fresh_root = DesignRoot::new(symbols.clone()); + add_standard_library(symbols.clone(), &mut fresh_root); + + let mut root = DesignRoot::new(symbols.clone()); + add_standard_library(symbols.clone(), &mut root); + + for (j, (library_name, code)) in codes.iter().enumerate() { + root.add_design_file(library_name.clone(), code.design_file()); + + if i != j { + fresh_root.add_design_file(library_name.clone(), code.design_file()); + } else { + fresh_root.ensure_library(library_name.clone()); + } + } + + let mut diagnostics = Vec::new(); + root.analyze(&mut diagnostics); + check_diagnostics(diagnostics, expected_diagnostics.clone()); + + let (library_name, code) = &codes[i]; + + // Remove a files + root.remove_source(library_name.clone(), code.source()); + check_analysis_equal(&mut root, &mut fresh_root); + + // Add back files again + root.add_design_file(library_name.clone(), code.design_file()); + fresh_root.add_design_file(library_name.clone(), code.design_file()); + + let diagnostics = check_analysis_equal(&mut root, &mut fresh_root); + + // Ensure expected diagnostics when all files are added + check_diagnostics(diagnostics, expected_diagnostics.clone()); + } +} + +fn check_analysis_equal(got: &mut DesignRoot, expected: &mut DesignRoot) -> Vec { + let mut got_diagnostics = Vec::new(); + got.analyze(&mut got_diagnostics); + + let mut expected_diagnostics = Vec::new(); + expected.analyze(&mut expected_diagnostics); + + // Check that diagnostics are equal to doing analysis from scratch + check_diagnostics(got_diagnostics.clone(), expected_diagnostics); + + // Check that all references are equal, ensures the incremental + // analysis has cleared references + let mut got_searcher = FindAnyReferences::default(); + let _ = got.search(&mut got_searcher); + + let mut expected_searcher = FindAnyReferences::default(); + let _ = expected.search(&mut expected_searcher); + + let got_refs: FnvHashSet<_> = got_searcher + .references + .into_iter() + .map(|id| got.get_ent(id).decl_pos()) + .collect(); + let expected_refs: FnvHashSet<_> = expected_searcher + .references + .into_iter() + .map(|id| expected.get_ent(id).decl_pos()) + .collect(); + let diff: FnvHashSet<_> = got_refs.symmetric_difference(&expected_refs).collect(); + assert_eq!(diff, FnvHashSet::default()); + + got_diagnostics +} + +/// Find any reference +/// Added to help ensure that there are no references to removed sources +#[derive(Default)] +struct FindAnyReferences { + references: Vec, +} + +impl Searcher for FindAnyReferences { + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + _: &SrcPos, + reference: &Reference, + ) -> SearchState { + if let Some(id) = reference.get() { + self.references.push(id); + }; + NotFinished + } +} diff --git a/vhdl_lang/src/analysis/tests/mod.rs b/vhdl_lang/src/analysis/tests/mod.rs new file mode 100644 index 0000000..e64a6e2 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/mod.rs @@ -0,0 +1,142 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +mod assignment_typecheck; +mod association_formal; +mod circular_dependencies; +mod context_clause; +mod custom_attributes; +mod declarations; +mod deferred_constant; +mod hierarchy; +mod homographs; +mod implicit; +mod incomplete_type; +mod incremental_analysis; +mod package_instance; +mod protected_type; +mod resolves_design_units; +mod resolves_names; +mod resolves_type_mark; +mod sensitivity_list; +mod subprogram_arguments; +mod subprogram_instance; +mod tool_directive; +mod typecheck_expression; +mod util; +mod view_declarations; +mod visibility; + +use std::cell::RefCell; +use std::path::PathBuf; +use vhdl_lang::TokenSpan; + +pub use self::util::*; +use crate::ast::Designator; +use crate::ast::UnitId; +pub use crate::data::Diagnostic; +use crate::data::NoDiagnostics; +pub use crate::syntax::test::*; +use crate::syntax::Token; + +use super::analyze::AnalyzeContext; +use super::scope::*; +use super::DesignRoot; +use crate::named_entity::*; +use crate::Source; + +pub(super) struct TestSetup<'a> { + builder: RefCell, + root: DesignRoot, + arena: Arena, + pub scope: Scope<'a>, +} + +impl<'a> TestSetup<'a> { + pub fn new() -> Self { + let builder = LibraryBuilder::new(); + let (mut root, _) = builder.get_analyzed_root(); + root.ensure_library(root.symbol_utf8("libname")); + let arena = Arena::new(ArenaId::default()); + + Self { + arena, + root, + builder: RefCell::new(builder), + scope: Scope::new(Region::default()), + } + } + + #[allow(clippy::ptr_arg)] + pub fn ctx<'t>(&'a self, tokens: &'t Vec) -> AnalyzeContext<'a, 't> { + let ctx = AnalyzeContext::new( + &self.root, + &UnitId::package( + &self.root.symbol_utf8("libname"), + &self.root.symbol_utf8("dummy"), + ), + Source::inline(&PathBuf::new(), ""), + &self.arena, + tokens, + ); + ctx.add_implicit_context_clause(&self.scope).unwrap(); + ctx + } + + pub fn snippet(&self, code: &str) -> Code { + self.builder.borrow_mut().snippet(code) + } + + pub fn declarative_part(&'a self, code: &str) -> Code { + let code = self.snippet(code); + let dummy_parent = self.arena.alloc( + Designator::Anonymous(0), + None, + Related::None, + AnyEntKind::Library, + None, + TokenSpan::for_library(), + Some(code.source().clone()), + ); + self.ctx(&code.tokenize()) + .analyze_declarative_part( + &self.scope, + dummy_parent, + code.declarative_part().as_mut(), + &mut NoDiagnostics, + ) + .unwrap(); + code + } + + pub fn lookup(&'a self, sym: &str) -> EntRef<'a> { + // We cheat and create a source pos as the lookup method requires it + let designator = self.snippet(sym).designator(); + + self.scope + .lookup(&designator.item) + .unwrap() + .into_non_overloaded() + .unwrap() + } + + pub fn lookup_overloaded(&'a self, code: Code) -> OverloadedEnt<'a> { + let des = code.designator(); + + if let NamedEntities::Overloaded(overloaded) = self.scope.lookup(&des.item).unwrap() { + overloaded + .entities() + .find(|ent| ent.decl_pos() == Some(&code.pos())) + .unwrap() + } else { + panic!("Expected overloaded name"); + } + } + + pub fn lookup_type(&'a self, sym: &str) -> TypeEnt<'a> { + TypeEnt::from_any(self.lookup(sym)).unwrap() + } +} diff --git a/vhdl_lang/src/analysis/tests/package_instance.rs b/vhdl_lang/src/analysis/tests/package_instance.rs new file mode 100644 index 0000000..ead876b --- /dev/null +++ b/vhdl_lang/src/analysis/tests/package_instance.rs @@ -0,0 +1,687 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use pretty_assertions::assert_eq; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn package_name_must_be_visible_in_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is +generic (const : natural); +end package; + +package ipkg_err is new gpkg generic map (const => 0); +package ipkg_ok is new work.gpkg generic map (const => 0); + +package nested is +package ipkg_err is new gpkg generic map (const => 0); +package ipkg_ok is new work.gpkg generic map (const => 0); +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("gpkg", 2), + "No declaration of 'gpkg'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("gpkg", 4), + "No declaration of 'gpkg'", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn package_name_must_be_an_uninstantiated_package_in_package_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +constant const : natural := 0; +end package; + +package ipkg is new work.pkg generic map (const => 0); + +package nested is +package ipkg2 is new work.pkg.const generic map (const => 0); +end package; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s1("work.pkg"), + "'work.pkg' is not an uninstantiated generic package", + ), + Diagnostic::mismatched_kinds( + code.s1("work.pkg.const"), + "'work.pkg.const' is not an uninstantiated generic package", + ), + ], + ); +} + +#[test] +fn resolves_generic_package_interface_list() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic (type type_t); +end package; + +package ipkg1 is new work.gpkg + generic map ( + missing => integer + ); + +package ipkg2 is new work.gpkg + generic map ( + type_t => missing + ); + +package ipkg3 is new work.gpkg + generic map ( + type_t => integer + ); +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("missing", 1), + "No declaration of 'missing'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("missing", 2), + "No declaration of 'missing'", + ErrorCode::Unresolved, + ), + ], + ); + + let typ = root + .search_reference( + code.source(), + code.s1("type_t => integer").s1("type_t").pos().start(), + ) + .unwrap(); + assert_eq!(typ.decl_pos(), Some(&code.s1("type_t").pos())); + assert_eq!(root.format_declaration(typ), Some("type type_t".to_owned())); +} + +#[test] +fn generic_package_interface_kind_mismatch() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t; + constant c0 : integer + ); +end package; + +package good_pkg is new work.gpkg + generic map ( + type_t => integer, + c0 => 0 + ); + +package bad_pkg1 is new work.gpkg + generic map ( + type_t => 16#bad#, + c0 => natural + ); + +package bad_pkg2 is new work.gpkg + generic map ( + type_t => work, + c0 => 0 + ); + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s("16#bad#", 1), + "Cannot map expression to type generic", + ), + Diagnostic::mismatched_kinds( + code.s1("natural"), + "subtype 'NATURAL' cannot be used in an expression", + ), + Diagnostic::mismatched_kinds( + code.s1("=> work").s1("work"), + "Expected type, got library libname", + ), + ], + ); +} + +#[test] +fn subtype_constraints_are_supported() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is + type rec_t is record + field: integer_vector; + end record; +end package; + +package gpkg is + generic ( + type type_t + ); +end package; + +package arr_pkg is new work.gpkg + generic map ( + type_t => integer_vector(0 to 3) + ); + +use work.pkg.rec_t; +package rec_pkg is new work.gpkg + generic map ( + type_t => rec_t(field(0 to 3)) + ); +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn interface_subprogram() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t; + function to_string(value : type_t) return string + ); +end package; + +package pkg is + package ipkg is new work.gpkg + generic map ( + type_t => integer, + to_string => to_string); +end package; + +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let typ = root + .search_reference( + code.source(), + code.s1("to_string => to_string") + .s1("to_string") + .pos() + .start(), + ) + .unwrap(); + assert_eq!(typ.decl_pos(), Some(&code.s1("to_string").pos())); + assert!(root.format_declaration(typ).is_some()); + + assert!(root + .search_reference(code.source(), code.sa("to_string => ", "to_string").start()) + .is_some()); +} + +#[test] +fn interface_subprogram_rhs_not_a_subprogram() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t; + function to_string(value : type_t) return string + ); +end package; + +package pkg is + package ipkg is new work.gpkg + generic map ( + type_t => integer, + to_string => character); +end package; + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s1("character"), + "Cannot map type 'CHARACTER' to subprogram generic", + )], + ); +} + +#[test] +fn interface_subprogram_rhs_does_not_match_signature() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t; + function to_string(value : type_t) return string + ); +end package; + +package pkg is + function my_to_string(arg: real) return string; + + package ipkg is new work.gpkg + generic map ( + type_t => integer, + to_string => my_to_string); +end package; + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.sa("to_string => ", "my_to_string"), + "Cannot map 'my_to_string' to subprogram generic to_string[INTEGER return STRING]", + ) + .related( + code.s1("my_to_string"), + "Does not match function my_to_string[REAL return STRING]", + )], + ); +} + +#[test] +fn interface_subprogram_operator() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t; + function match(l, r : type_t) return boolean + ); +end package; + +package pkg is + package ipkg is new work.gpkg + generic map ( + type_t => integer, + match => \"=\"); + + package bad_pkg is new work.gpkg + generic map ( + type_t => integer, + match => \"invalid\"); +end package; + +", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("\"invalid\""), + "Invalid operator symbol", + ErrorCode::InvalidOperatorSymbol, + )], + ); +} + +#[test] +fn interface_package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg_base is + generic ( + type type_t + ); +end package; + +package gpkg is + generic ( + package iface_pkg is new work.gpkg_base generic map (<>) + ); +end package; + +package ipkg_base is new work.gpkg_base +generic map ( + type_t => integer); + +package ipkg is new work.gpkg +generic map ( + iface_pkg => work.ipkg_base +); +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let typ = root + .search_reference( + code.source(), + code.s1("iface_pkg => ").s1("iface_pkg").pos().start(), + ) + .unwrap(); + assert_eq!(typ.decl_pos(), Some(&code.s1("iface_pkg").pos())); + assert!(root.format_declaration(typ).is_some()); +} + +#[test] +fn generics_are_not_visible_by_selection() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t + ); + + constant also_visible : natural := 0; +end package; + +package pkg is + package ipkg is new work.gpkg + generic map ( + type_t => integer); + + subtype sub_t is ipkg.type_t; + constant c1 : natural := ipkg.also_visible; +end package; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("ipkg.type_t").s1("type_t"), + "No declaration of 'type_t' within package instance 'ipkg'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn intantiate_items_with_correct_type() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package gpkg is + generic ( + type type_t; + value: type_t + ); + subtype sub_t is type_t; +end package; + +package pkg is + package ipkg is new work.gpkg + generic map ( + type_t => integer, + value => 0); + + constant c1 : ipkg.sub_t := 0; +end package; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn interface_type_has_comparison_operations() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package gpkg is + generic ( + type type_t + ); + + procedure check(a, b : type_t); +end package; + +package body gpkg is + procedure check(a, b : type_t) is + constant c0 : boolean := a = b; + constant c1 : boolean := a /= b; + begin + end; + +end package body; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn hover_and_references_for_instantiated_entities() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t + ); + subtype sub_t is type_t; +end package; + +package pkg is + package ipkg is new work.gpkg + generic map ( + type_t => integer); + + constant c1 : ipkg.sub_t := 0; +end package; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let sub_t = root + .search_reference( + code.source(), + code.s1("ipkg.sub_t").s1("sub_t").pos().start(), + ) + .unwrap(); + + assert_eq!(sub_t.decl_pos(), Some(&code.s1("sub_t").pos())); + + assert_eq!( + root.format_declaration(sub_t), + Some("subtype sub_t is type_t;".to_owned()) + ); + + assert_eq!( + root.find_all_references(sub_t), + vec![code.s("sub_t", 1).pos(), code.s("sub_t", 2).pos()] + ); + + // Ensure find all reference from within generic package also matches + assert_eq!( + root.find_all_references( + root.search_reference(code.source(), code.s1("sub_t").pos().start()) + .unwrap() + ), + vec![code.s("sub_t", 1).pos(), code.s("sub_t", 2).pos()] + ); +} + +#[test] +fn references_of_instantiated_do_not_include_siblings() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package gpkg is + generic ( + type type_t + ); + subtype sub_t is type_t; +end package; + +package pkg is + package ipkg0 is new work.gpkg + generic map ( + type_t => integer); + + package ipkg1 is new work.gpkg + generic map ( + type_t => natural); + + constant c0 : ipkg0.sub_t := 0; + constant c1 : ipkg1.sub_t := 0; +end package; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + // References from parent matches all sibling + assert_eq!( + root.find_all_references( + root.search_reference(code.source(), code.s1("sub_t").pos().start()) + .unwrap() + ), + vec![ + code.s("sub_t", 1).pos(), + code.s("sub_t", 2).pos(), + code.s("sub_t", 3).pos() + ] + ); + + // Siblings only include parent + assert_eq!( + root.find_all_references( + root.search_reference(code.source(), code.s("sub_t", 2).pos().start()) + .unwrap() + ), + vec![code.s("sub_t", 1).pos(), code.s("sub_t", 2).pos(),] + ); + + // Siblings only include parent + assert_eq!( + root.find_all_references( + root.search_reference(code.source(), code.s("sub_t", 3).pos().start()) + .unwrap() + ), + vec![code.s("sub_t", 1).pos(), code.s("sub_t", 3).pos(),] + ); +} + +#[test] +pub fn using_all_from_generic_package() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package foo is + generic ( + x: natural := 1 + ); +end package; + +package bar is + generic ( + package foo0 is new work.foo + generic map (<>) + ); + use foo0.all; +end package; + ", + ); + + let diag = builder.analyze(); + check_no_diagnostics(&diag); +} + +#[test] +pub fn aliases_in_generic_packages() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package test_pkg is + generic (N : integer); + type my_type is array (N - 1 downto 0) of bit; + alias also_my_type is my_type; +end package; + +package pkg_inst is new work.test_pkg generic map (N => 8); +use work.pkg_inst.all; + +entity test_top_entity is +end entity; + +architecture rtl of test_top_entity is + signal my_sig : my_type; + signal my_sig2: also_my_type; +begin + my_sig <= my_sig2; +end architecture; + ", + ); + + let diag = builder.analyze(); + check_no_diagnostics(&diag); +} diff --git a/vhdl_lang/src/analysis/tests/protected_type.rs b/vhdl_lang/src/analysis/tests/protected_type.rs new file mode 100644 index 0000000..516a839 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/protected_type.rs @@ -0,0 +1,363 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn error_on_missing_protected_body() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg_no_body is +type a1 is protected +end protected; +end package; + +package pkg is +type b1 is protected +end protected; +end package; + +package body pkg is +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("a1"), + "Missing body for protected type 'a1'", + ErrorCode::MissingProtectedBodyType, + ), + Diagnostic::new( + code.s1("b1"), + "Missing body for protected type 'b1'", + ErrorCode::MissingProtectedBodyType, + ), + ], + ); +} + +#[test] +fn error_on_missing_protected_type_for_body() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg_no_body is + type a1 is protected body + end protected body; +end package; + +package pkg is +end package; + +package body pkg is +type b1 is protected body +end protected body; + +type b1 is protected +end protected; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("a1"), + "No declaration of protected type 'a1'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("b1"), + "No declaration of protected type 'b1'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("b1", 2), + "Missing body for protected type 'b1'", + ErrorCode::MissingProtectedBodyType, + ), + ], + ); +} + +#[test] +fn allows_protected_type_and_body_with_same_name() { + check_code_with_no_diagnostics( + " +package pkg is + type prot_t is protected + end protected; + + type prot_t is protected body + end protected body; +end package; +", + ); +} + +#[test] +fn allows_protected_type_and_body_in_package_header_and_body() { + check_code_with_no_diagnostics( + " +package pkg is + type prot_t is protected + end protected; +end package; + +package body pkg is + type prot_t is protected body + end protected body; +end package body; +", + ); +} + +#[test] +fn forbid_duplicate_protected_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type prot_t is protected + end protected; + + type prot_t is protected + end protected; + + type prot_t is protected body + end protected body; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, duplicates(&code, &["prot_t"])); +} + +#[test] +fn forbid_duplicate_protected_type_body() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type prot_t is protected + end protected; + + type prot_t is protected body + end protected body; + + type prot_t is protected body + end protected body; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, vec![duplicate(&code, "prot_t", 2, 3)]); +} + +#[test] +fn protected_type_is_visible_in_declaration() { + check_code_with_no_diagnostics( + " +package pkg1 is + type prot_t is protected + procedure proc(val : inout prot_t); + end protected; + + type prot_t is protected body + procedure proc(val : inout prot_t) is + begin + end; + end protected body; +end package;", + ); +} + +#[test] +fn forbid_incompatible_deferred_items() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + + -- Protected type vs constant + type a1 is protected + end protected; + constant a1 : natural := 0; + + -- Just to avoid missing body error + type a1 is protected body + end protected body; + + -- Deferred constant vs protected body + constant b1 : natural; + + type b1 is protected body + end protected body; + +end package; + +package body pkg is + constant b1 : natural := 0; +end package body; +", + ); + + let diagnostics = builder.analyze(); + let expected = vec![ + duplicate(&code, "a1", 1, 2), + Diagnostic::new( + code.s("b1", 2), + "'b1' is not a protected type", + ErrorCode::TypeMismatch, + ), + ]; + check_diagnostics(diagnostics, expected); +} + +#[test] +fn protected_type_body_extends_declaration() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + type prot_t is protected + function fun1 return natural; + function fun2 return natural; + end protected; + + type prot_t is protected body + -- Function 2 should be visible before + function fun1 return natural is + begin + return fun2; + end; + + function private return natural is + begin + return missing; + end; + + function fun2 return natural is + begin + return 0; + end; + end protected body; +end package;", + ); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, vec![missing(&code, "missing", 1)]); +} + +#[test] +fn protected_type_body_reference() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent1 is +end ent1; + +architecture arch of ent1 is + type prot_t is protected + end protected; + + type prot_t is protected body + end protected body; + + shared variable var : prot_t; +begin +end architecture;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("prot_t", 2).start()), + Some(code.s("prot_t", 2).pos()) + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("prot_t", 3).start()), + Some(code.s("prot_t", 2).pos()) + ); + + let ptype = root + .search_reference(code.source(), code.s1("prot_t").start()) + .unwrap(); + assert_eq!( + root.find_all_references(ptype), + vec![ + code.s("prot_t", 1).pos(), + code.s("prot_t", 2).pos(), + code.s("prot_t", 3).pos() + ] + ); + + assert_eq!( + root.find_definition_of(ptype).unwrap().decl_pos(), + Some(&code.s("prot_t", 2).pos()) + ); +} + +/// This was a bug where a procedure declared between the protected type and the body +/// did not have the same signature as the definition which was after the body +#[test] +fn protected_type_and_body_result_in_the_same_signature() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + type prot_t is protected + end protected; + + procedure myproc(arg : prot_t); + + type prot_t is protected body + end protected body; + + procedure myproc(arg : prot_t) is + begin + end procedure; + +end package body; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.find_all_references_pos(&code.s1("prot_t").pos()).len(), + 4 + ); + + assert_eq!( + root.find_all_references_pos(&code.s1("myproc").pos()).len(), + 2 + ); +} diff --git a/vhdl_lang/src/analysis/tests/resolves_design_units.rs b/vhdl_lang/src/analysis/tests/resolves_design_units.rs new file mode 100644 index 0000000..07681b7 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/resolves_design_units.rs @@ -0,0 +1,550 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::resolves_type_mark::kind_error; +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn error_on_configuration_before_entity_in_same_file() { + let mut builder = LibraryBuilder::new(); + + let code = builder.code( + "libname", + " +configuration cfg of ent is +for rtl +end for; +end configuration; + +entity ent is +end entity; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("cfg", 1), + "Configuration 'cfg' declared before entity 'ent'", + ErrorCode::DeclaredBefore, + )], + ); +} + +#[test] +fn error_on_configuration_of_missing_entity() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +configuration cfg of ent is +for rtl +end for; +end configuration; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("ent", 1), + "No primary unit 'ent' within library 'libname'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn good_configurations() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +configuration cfg_good1 of ent is +for rtl +end for; +end configuration; + +configuration cfg_good2 of work.ent is +for rtl +end for; +end configuration; + +library libname; +configuration cfg_good3 of libname.ent is +for rtl +end for; +end configuration; +", + ); + + check_no_diagnostics(&builder.analyze()); +} + +#[test] +fn error_on_configuration_of_entity_outside_of_library() { + let mut builder = LibraryBuilder::new(); + builder.code( + "lib2", + " +entity ent is +end entity;", + ); + let code = builder.code( + "libname", + " +library lib2; + +configuration cfg of lib2.ent is +for rtl +end for; +end configuration; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("lib2", 2), + "Configuration must be within the same library 'libname' as the corresponding entity", + ErrorCode::ConfigNotInSameLibrary, + )], + ); +} + +#[test] +fn search_reference_from_configuration_to_entity() { + check_search_reference( + " +entity decl is +end entity; + +configuration cfg_good1 of decl is +for rtl +end for; +end configuration; + +configuration cfg_good2 of work.decl is +for rtl +end for; +end configuration; +", + ); +} + +#[test] +fn error_on_architecture_of_missing_entity() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +architecture a of missing is +begin +end architecture; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("missing", 1), + "No primary unit 'missing' within library 'libname'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn error_on_architecture_before_entity_in_same_file() { + let mut builder = LibraryBuilder::new(); + + let code = builder.code( + "libname", + " +architecture aname of ent is +begin +end architecture; + +entity ent is +end entity; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("aname", 1), + "Architecture 'aname' of 'ent' declared before entity 'ent'", + ErrorCode::DeclaredBefore, + )], + ); +} + +#[test] +fn error_on_body_of_missing_package() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package body missing is +end package body; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("missing", 1), + "No primary unit 'missing' within library 'libname'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn error_on_package_body_before_package_in_same_file() { + let mut builder = LibraryBuilder::new(); + + let code = builder.code( + "libname", + " +package body pkg is +end package body; + +package pkg is +end package; +", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("pkg", 1), + "Package body 'pkg' declared before package 'pkg'", + ErrorCode::DeclaredBefore, + )], + ); +} + +#[test] +fn resolves_reference_from_architecture_to_entity() { + check_search_reference( + " +entity decl is +end entity; + +architecture a of decl is +begin +end architecture; +", + ); +} + +#[test] +fn resolves_reference_from_package_body_to_package() { + check_search_reference( + " +package decl is +end package; + +package body decl is +end package body; +", + ); +} + +#[test] +fn resolves_reference_to_entity_instance() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ename1 is +end entity; + +architecture a of ename1 is +begin +end architecture; + +entity ename2 is +end entity; + +architecture a of ename2 is +begin + bad_inst : entity work.missing; + inst : entity work.ename1; +end architecture; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No primary unit 'missing' within library 'libname'", + ErrorCode::Unresolved, + )], + ); + + // From reference position + assert_eq!( + root.search_reference_pos(code.source(), code.s("ename1", 3).start()), + Some(code.s("ename1", 1).pos()) + ); + + // Find all references + assert_eq_unordered( + &root.find_all_references_pos(&code.s1("ename1").pos()), + &[ + code.s("ename1", 1).pos(), + code.s("ename1", 2).pos(), + code.s("ename1", 3).pos(), + ], + ); +} + +#[test] +fn resolves_component_instance() { + check_missing( + " +entity ent is +end entity; + +architecture a of ent is +begin + inst : component missing; +end architecture; +", + ); +} + +#[test] +fn search_component_instance() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + component decl is + end component; +begin + inst : component decl; +end architecture; +", + ); +} + +#[test] +fn resolves_configuration_instance() { + check_missing( + " +entity ent is +end entity; + +architecture a of ent is +begin + inst : configuration missing; +end architecture; +", + ); +} + +#[test] +fn search_configuration_instance() { + check_search_reference( + " +entity ent is +end entity; + +configuration decl of ent is + for a + end for; +end configuration; + +architecture a of ent is +begin + inst : configuration work.decl; +end architecture; + + +", + ); +} + +#[test] +fn resolves_reference_to_package_body() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is +end package body; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + // From declaration position + assert_eq!( + root.search_reference_pos(code.source(), code.s("pkg", 1).start()), + Some(code.s("pkg", 1).pos()) + ); + + // From reference position + assert_eq!( + root.search_reference(code.source(), code.s("pkg", 2).start()) + .unwrap() + .declaration() + .decl_pos(), + Some(&code.s("pkg", 1).pos()) + ); + + // Find all references + assert_eq_unordered( + &root.find_all_references_pos(&code.s1("pkg").pos()), + &[code.s("pkg", 1).pos(), code.s("pkg", 2).pos()], + ); + assert_eq_unordered( + &root.find_all_references_pos(&code.s("pkg", 2).pos()), + &[code.s("pkg", 1).pos(), code.s("pkg", 2).pos()], + ); +} + +#[test] +fn component_instantiation_is_correct() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type enum_t is (alpha, beta); +begin + inst : component enum_t; +end architecture; + + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error( + &code, + "enum_t", + 2, + 1, + "component", + "type 'enum_t'", + )], + ); +} + +#[test] +fn entity_instantiation_is_correct() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package bad is +end package; + +entity ent is +end entity; + +architecture a of ent is +begin + inst : entity work.bad; +end architecture; + + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error(&code, "bad", 2, 1, "entity", "package 'bad'")], + ); +} + +#[test] +fn configuration_instantiation_is_correct() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity bad is +end entity; + +entity ent is +end entity; + +architecture a of ent is +begin + inst : configuration work.bad; +end architecture; + + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error( + &code, + "bad", + 2, + 1, + "configuration", + "entity 'bad'", + )], + ); +} + +#[test] +fn empty_component_instantiation() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + component empty + end component; +begin + inst: empty; +end architecture; + + +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + assert_eq!( + root.search_reference(code.source(), code.sa("inst: ", "empty").start()) + .unwrap() + .decl_pos(), + Some(&code.s1("empty").pos()) + ); +} diff --git a/vhdl_lang/src/analysis/tests/resolves_names.rs b/vhdl_lang/src/analysis/tests/resolves_names.rs new file mode 100644 index 0000000..a9527e8 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/resolves_names.rs @@ -0,0 +1,2187 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use pretty_assertions::assert_eq; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn resolves_names_in_object_decl_init_expressions() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + constant c0 : natural := 0; + constant c1 : natural := c0; + constant c2 : natural := missing; + constant c3 : natural := 1 + missing; + constant c4 : natural := - missing; +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + (1..=3).map(|idx| missing(&code, "missing", idx)).collect(), + ); + + // Goto declaration from declaration + assert_eq!( + root.search_reference_pos(code.source(), code.s("c0", 1).end()), + Some(code.s("c0", 1).pos()) + ); + + // Goto declaration from reference + assert_eq!( + root.search_reference_pos(code.source(), code.s("c0", 2).end()), + Some(code.s("c0", 1).pos()) + ); +} + +#[test] +fn resolves_names_in_iface_object_decl_init_expressions() { + check_missing( + " +package pkg is + function foo(constant c2 : natural := missing) return natural; +end package;", + ); +} + +#[test] +fn search_names_in_iface_object_decl_init_expressions() { + check_search_reference( + " + package pkg is + constant decl : natural := 0; + function foo(constant c2 : natural := decl) return natural; + end package;", + ); +} + +#[test] +fn subprogram_parameters_are_visible_in_body() { + check_missing( + " +package pkg is +end package; + +package body pkg is + function foo(c0 : natural) return natural is + constant c1 : natural := c0; + constant c2 : natural := missing; + begin + return c1; + end; +end package body; +", + ); +} + +/// Check that at least the prefix is resolved also for names which are not purely selected names +#[test] +fn resolves_names_for_prefix_of_non_selected() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type arr_t is array (natural range 0 to 1) of integer_vector(0 to 1); + constant c0 : integer_vector(0 to 1) := (0,1); + constant c1 : natural := c0(0); + constant c2 : natural := missing(0); + constant c3 : integer_vector(0 to 1) := c0(0 to 1); + constant c4 : integer_vector(0 to 1) := missing(0 to 1); + constant c5 : arr_t := (0 => (0,1), 1 => (2, 3)); + + -- This was a bug at one point + constant c6 : natural := c5(0)'length; + constant c7 : natural := missing(0)'length; + + -- This was also a bug at one point + type rec_t is record + field : natural; + end record; + type rec_arr_t is array (natural range <>) of rec_t; + constant c8 : rec_arr_t(0 to 0) := (0 => (field => 0)); + constant c9 : natural := c8(0).field; + constant ca : natural := missing(0).field; + constant cb : rec_t := (field => 0); + constant cc : natural := cb.field; + constant cd : natural := missing.field; + +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + (1..=5).map(|idx| missing(&code, "missing", idx)).collect(), + ); + + // Goto declaration from reference that is prefix of indexed/function call + assert_eq!( + root.search_reference_pos(code.source(), code.s("c0", 2).end()), + Some(code.s("c0", 1).pos()) + ); + + // Goto declaration from reference that is prefix of slice + assert_eq!( + root.search_reference_pos(code.source(), code.s("c0", 3).end()), + Some(code.s("c0", 1).pos()) + ); +} + +#[test] +fn labels_are_visible() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin + lab1 : process + constant name1 : string := lab1'instance_name; + constant dummy : string := missing'instance_name; + begin + end process; + + lab2 : block is + constant name1 : string := lab1'instance_name; + constant name2 : string := lab2'instance_name; + constant dummy : string := missing'instance_name; + begin + end block; +end architecture; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + (1..=2).map(|idx| missing(&code, "missing", idx)).collect(), + ); + + for i in 1..=3 { + assert_eq!( + root.search_reference_pos(code.source(), code.s("lab1", i).end()), + Some(code.s("lab1", 1).pos()) + ); + } + + for i in 1..=2 { + assert_eq!( + root.search_reference_pos(code.source(), code.s("lab2", i).end()), + Some(code.s("lab2", 1).pos()) + ); + } +} + +#[test] +fn resolves_names_in_discrete_ranges() { + check_missing( + " +package pkg is + type arr0_t is array (natural range missing to 0) of natural; + type arr1_t is array (natural range 0 to missing) of natural; + type arr2_t is array (missing to 0) of natural; + type arr3_t is array (0 to missing) of natural; + type arr4_t is array (missing'range) of natural; +end package; +", + ); +} + +#[test] +fn search_names_in_discrete_ranges() { + check_search_reference( + " +package pkg is + constant decl : integer_vector(0 to 1) := (others => 0); + type arr_t is array (natural range decl(0) to decl(0)) of natural; + type arr2_t is array (decl(0) to decl(0)) of natural; + type arr3_t is array (decl'range) of natural; +end package; +", + ); +} + +#[test] +fn resolves_names_in_subtype_constraints() { + check_missing( + " +package pkg is + subtype sub1_t is integer_vector(missing to 0); + subtype sub2_t is integer range 0 to missing; + + type rec_t is record + field : integer_vector; + end record; + + subtype sub3_t is rec_t(field(missing to 0)); + + type uarr_t is array (natural range <>) of integer_vector; + subtype sub4_t is uarr_t(0 to missing); + +end package; +", + ); +} + +#[test] +fn search_names_in_subtype_constraints() { + check_search_reference( + " +package pkg is + constant decl : natural := 0; + subtype sub1_t is integer_vector(decl to decl); + subtype sub2_t is integer range decl to decl; + + type rec_t is record + field : integer_vector; + end record; + + subtype sub3_t is rec_t(field(decl to decl)); + + type uarr_t is array (natural range <>) of integer_vector; + subtype sub4_t is uarr_t(decl to decl); + +end package; +", + ); +} + +#[test] +fn search_names_in_integer_type_declaration_ranges() { + check_search_reference( + " +package pkg is + constant decl : natural := 0; + type int_t is range 0 to decl; +end package; +", + ); +} + +#[test] +fn search_names_in_integer_type_declaration() { + check_search_reference( + " +package pkg is + type decl is range 0 to 3; + constant foo : decl := 0; +end package; +", + ); +} + +#[test] +fn search_names_in_file_type_declaration() { + check_search_reference( + " +package pkg is + subtype decl is character; + type foo is file of decl; +end package; +", + ); +} + +#[test] +fn resolves_names_inside_names() { + check_missing( + " +package pkg is +end package; + +package body pkg is + type arr2d_t is array (natural range 0 to 1, natural range 0 to 1) of natural; + + function fun(a, b : natural) return natural is + begin + return 0; + end; + + function fun2 return natural is + -- Function call + constant c : natural := fun(a => missing, b => missing); + -- Function call + constant c2 : natural := fun(missing, missing); + + variable arr : arr2d_t; + -- Indexed + constant c3 : natural := arr(missing, missing); + + -- Slice + constant vec : integer_vector(0 to 1) := (0, 1); + constant c4 : integer_vector(0 to 1) := vec(missing to 0); + + constant c5 : natural := missing'val(0); + constant c6 : boolean := boolean'val(missing); + begin + end; + +end package body; +", + ); +} + +#[test] +fn search_names_in_inside_names() { + check_search_reference( + " +package pkg is +end package; + +package body pkg is + constant decl : natural := 0; + type arr2d_t is array (natural range 0 to 1, natural range 0 to 1) of natural; + + function fun(a, b : natural) return natural is + begin + return 0; + end; + + function fun2 return natural is + -- Function call + constant c : natural := fun(a => decl, b => decl); + -- Function call + constant c2 : natural := fun(decl, decl); + + variable arr : arr2d_t; + -- Indexed + constant c3 : natural := arr(decl, decl); + + -- Slice + constant vec : integer_vector(0 to 1) := (0, 1); + constant c4 : integer_vector(0 to 1) := vec(decl to decl); + + constant c5 : string := decl'simple_name; + constant c6 : boolean := boolean'val(decl); + begin + end; + +end package body; +", + ); +} + +#[test] +fn resolves_names_in_aggregates() { + check_missing( + " +package pkg is + -- Named + constant c0 : integer_vector(0 to 0) := (0 => missing); + constant c1 : integer_vector(0 to 0) := (missing to 0 => 0); + + -- Positional + constant c2 : integer_vector(0 to 1) := (missing, missing); +end package; +", + ); +} + +#[test] +fn search_names_in_aggregates() { + check_search_reference( + " +package pkg is + constant decl : natural := 0; + + -- Named + constant c0 : integer_vector(0 to 0) := (0 => decl); + constant c1 : integer_vector(0 to 0) := (decl to decl => 0); + + -- Positional + constant c2 : integer_vector(0 to 1) := (decl, decl); +end package; +", + ); +} + +#[test] +fn resolves_names_in_qualified_expr() { + check_missing( + " +package pkg is + -- Named + constant c0 : missing := missing'(1 + missing); +end package; +", + ); +} + +#[test] +fn search_names_in_qualified_expr() { + check_search_reference( + " +package pkg is + type decl is range 0 to 1; + -- Named + constant c0 : decl := decl'(decl'(0)); +end package; +", + ); +} + +#[test] +fn resolves_names_in_allocators() { + check_missing( + " +package pkg is +end package; + +package body pkg is + procedure p is + type acc_t is access integer_vector; + -- Qualified + variable ptr0 : acc_t := new integer_vector'(missing, missing); + -- Subtype + variable ptr1 : acc_t := new integer_vector(0 to missing); + begin + end procedure; +end package body; +", + ); +} + +#[test] +fn search_names_in_allocators() { + check_search_reference( + " +package pkg is +end package; + +package body pkg is + constant decl : natural := 0; + procedure p is + type acc_t is access integer_vector; + -- Qualified + variable ptr0 : acc_t := new integer_vector'(decl, decl); + -- Subtype + variable ptr1 : acc_t := new integer_vector(decl to decl); + begin + end procedure; +end package body; +", + ); +} + +#[test] +fn resolves_names_in_sequential_statements() { + check_missing( + " +package pkg is +end package; + +package body pkg is + procedure proc2(c : natural) is + begin + return; + end; + + function f return natural is + begin + -- Variable assignment + missing := missing; + missing := missing when missing else missing; + with missing select + missing := missing when missing, + missing when others; + + -- Procedure call + missing; + missing(missing); + + -- If statement + if missing = 1 then + missing; + elsif missing = 2 then + missing; + else + missing; + end if; + + -- Loops + for i in missing to 0 loop + for j in 0 to missing loop + end loop; + + proc2(i); -- Index is defined + missing; + + exit missing; + next missing; + end loop; + + + loop + missing; + next when missing; + exit when missing; + end loop; + + while missing loop + missing; + end loop; + + -- Case + case missing is + when missing => + missing; + when 0 to missing => + missing; + when missing to 0 => + missing; + end case; + + report missing severity missing; + assert missing report missing severity missing; + + -- Return + return missing; + end; +end package body; +", + ); +} + +#[test] +fn search_names_in_sequential_statements() { + check_search_reference( + " +package pkg is +end package; + +package body pkg is + procedure proc(c : natural) is + begin + end; + + function f return natural is + variable decl : natural := 0; + begin + -- Variable assignment + decl := decl; + decl := decl when decl = 0 else decl; + with decl select + decl := decl when decl, + decl when others; + + -- Procedure call + proc(decl); + + + -- If statement + if decl = 1 then + proc(decl); + elsif decl = 2 then + proc(decl); + else + proc(decl); + end if; + + -- Loops + for i in decl to decl loop + proc(decl); + end loop; + + loop + proc(decl); + next when decl = 0; + exit when decl = 0; + end loop; + + while decl = 0 loop + proc(decl); + end loop; + + -- Case + case decl is + when decl => + proc(decl); + when decl to decl => + proc(decl); + end case; + + report natural'image(decl) severity severity_level'val(decl); + assert decl = 0 report natural'image(decl) severity severity_level'val(decl); + + -- Return + return decl; + end; +end package body; +", + ); +} + +#[test] +fn check_missing_in_process_statements() { + check_missing( + " +entity ent is +end entity; + +architecture a of ent is +begin + main : process(missing) is + begin + wait on missing until missing = 0 ns for missing; + missing <= missing after missing; + missing <= force missing; + missing <= release; + with missing select + missing <= missing when missing, + missing when others; + + end process; +end architecture; +", + ); +} + +#[test] +fn search_in_process_statements() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + signal decl : time; +begin + main : process (decl) is + begin + wait on decl until decl = 0 ns for decl; + decl <= decl after decl; + decl <= force decl; + decl <= release; + with decl select + decl <= decl when decl, + decl when others; + end process; +end architecture; +", + ); +} + +#[test] +fn search_in_aggregate_target() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + signal decl : natural; +begin + main : process is + begin + (0 => decl) := (0 => 1); + end process; +end architecture; +", + ); +} + +#[test] +fn check_missing_in_instantiations() { + check_missing( + " +entity ename is + generic (g : natural); + port (s : natural); +end entity; + +entity ent is +end entity; + +architecture a of ent is + component comp is + generic (g : natural); + port (s : natural); + end component; +begin + inst: entity work.ename + generic map ( + g => missing) + port map ( + s => missing); + + inst2: component comp + generic map ( + g => missing) + port map ( + s => missing); +end architecture; +", + ); +} + +#[test] +fn check_search_in_instantiations() { + check_search_reference( + " +entity ename is + generic (g : natural); + port (s : natural); +end entity; + +entity ent is +end entity; + +architecture a of ent is + component comp is + generic (g : natural); + port (s : natural); + end component; + + constant decl : natural := 0; +begin + inst: entity work.ename + generic map ( + g => decl) + port map ( + s => decl); + + inst2: component comp + generic map ( + g => decl) + port map ( + s => decl); +end architecture; +", + ); +} + +#[test] +fn package_name_visible_in_header_and_body() { + check_code_with_no_diagnostics( + " +package pkg is + constant name1 : string := pkg'instance_name; +end package; + +package body pkg is + constant name2 : string := pkg'instance_name; +end package body; +", + ); +} + +#[test] +fn search_package_name_in_header_and_body() { + check_search_reference( + " +package decl is + constant name1 : string := decl'instance_name; +end package; + +package body decl is + constant name2 : string := decl'instance_name; +end package body; +", + ); +} + +#[test] +fn unit_name_visible_in_entity_architecture() { + check_code_with_no_diagnostics( + " +entity ent is +begin + p1 : process is + begin + report ent'instance_name; + end process; +end entity; + +architecture a of ent is +begin + p2 : process is + begin + report ent'instance_name; + report a'instance_name; + end process; +end; +", + ); +} + +#[test] +fn search_entity_name_in_entity() { + check_search_reference( + " +entity decl is +end entity; + +architecture a of decl is +begin + main : process is + begin + report decl'instance_name; + end process; +end architecture; + +", + ); +} + +#[test] +fn resolves_names_in_concurrent_statements() { + check_missing( + " +entity ent is +end entity; + +architecture a of ent is +begin + missing <= missing; + missing <= missing when missing else missing; + with missing select + missing <= missing when missing, + missing when others; + missing(missing); + assert missing report missing severity missing; +end architecture; +", + ); +} + +#[test] +fn search_names_in_concurrent_statements() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + procedure proc(signal sig : in natural) is + begin + end; + + signal decl : natural := 0; +begin + decl <= decl; + decl <= decl when decl = 0 else decl; + with decl select + decl <= decl when decl, + decl when others; + proc(decl); + assert decl = 0 report decl'instance_name severity severity_level'val(decl); +end architecture; +", + ); +} + +#[test] +fn search_for_loop_index() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is +begin +main : process is +begin + for decl in 0 to 3 loop + report integer'image(decl); + end loop; +end process; +end architecture; + +", + ); +} + +#[test] +fn search_for_generate_index_range() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + constant decl : natural := 0; +begin + gen: for i in decl to decl + 3 generate + end generate; +end architecture; +", + ); +} + +#[test] +fn search_for_generate_index() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + signal foo : integer_vector(0 to 3); +begin + gen: for decl in foo'range generate + foo(decl) <= 0; + end generate; +end architecture; + +", + ); +} + +#[test] +fn search_if_generate_conditions() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is + constant decl : natural := 0; + signal foo : natural; +begin + gen: if decl = 0 generate + foo <= decl; + else generate + foo <= decl + 1; + end generate; +end architecture; +", + ); +} + +#[test] +fn search_generate_alternate_labels() { + check_search_reference( + " +entity ent is +end entity; + +architecture a of ent is +begin + gen: if decl: true generate + end generate; +end architecture; +", + ); +} + +#[test] +fn resolves_missing_name_in_alias() { + check_missing( + " +package pkg is + alias a is missing[natural]; + alias b is maximum[missing]; +end package; +", + ); +} + +#[test] +fn search_name_in_alias() { + check_search_reference( + " +package pkg is + type decl is (alpha, beta); + function fun(arg : decl) return decl; + procedure proc(arg : decl); + alias a is fun[decl return decl]; + alias b is proc[decl]; + alias c is decl; +end package; +", + ); +} + +#[test] +fn search_external_name() { + check_search_reference( + " +package pkg is + type decl is (alpha, beta); +end package; + +entity ent2 is +end entity; + +use work.pkg.all; + +architecture a of ent2 is + signal foo : decl; +begin +end architecture; + +entity ent is +end entity; + +use work.pkg.all; + +architecture a of ent is + signal foo : decl; +begin + inst : entity work.ent2; + foo <= << signal inst.foo : decl >>; +end architecture; +", + ); +} + +#[test] +fn block_names_are_visible() { + check_code_with_no_diagnostics( + " +entity ent is + port (ent_in : integer); +end entity; + +architecture a of ent is + signal sig : integer; +begin + blk: block (ent_in = 1) is + generic( gen : integer := 0 ); + generic map ( gen => 1); + port( + prt_in : in integer := 0; + prt_out : out integer := 0 + ); + port map ( + prt_in => ent_in + sig, + prt_out => open + ); + begin + prt_out <= gen + prt_in + sig; + end block; +end architecture; +", + ); +} + +#[test] +fn error_on_signature_for_non_overloaded_alias() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type enum_t is (alpha, beta); + alias alias_t is enum_t[return integer]; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("[return integer]"), + "Alias should only have a signature for subprograms and enum literals", + ErrorCode::IllegalSignature, + )], + ); +} + +#[test] +fn error_on_non_signature_for_overloaded_alias() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + procedure subpgm(arg: natural) is + begin + end; + + alias alias_t is subpgm; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("subpgm", 2), + "Signature required for alias of subprogram and enum literals", + ErrorCode::SignatureRequired, + )], + ); +} + +#[test] +fn signatures_are_compared_with_base_type() { + check_code_with_no_diagnostics( + " +package pkg is +end package; + +package body pkg is + subtype sub_type is natural range 0 to 5; + alias type_alias is sub_type; + subtype sub_type2 is type_alias range 0 to 2; + + function subpgm(arg: sub_type2) return sub_type2 is + begin + end; + + alias alias1 is subpgm[integer return integer]; + alias alias2 is subpgm[type_alias return type_alias]; + alias alias3 is subpgm[sub_type return sub_type]; + alias alias4 is subpgm[sub_type2 return sub_type2]; +end package body; +", + ); +} + +#[test] +fn can_goto_declaration_of_alias_with_signature() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + function subpgm(arg: natural) return natural is + begin + end; + + function subpgm(arg: boolean) return boolean is + begin + end; + + alias alias1 is subpgm[boolean return boolean]; + alias alias2 is subpgm[integer return integer]; +end package body; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + // Goto declaration from declaration + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 3).end()), + Some(code.s("subpgm", 2).pos()) + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 4).end()), + Some(code.s("subpgm", 1).pos()) + ); +} + +#[test] +fn overloaded_name_can_be_selected() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + type rec_t is record + f : natural; + end record; + + function foo return rec_t is + variable t : rec_t; + begin + return t; + end function; + + procedure bar is + variable s : natural; + begin + s := foo.f; + end; +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn record_fields_are_resolved() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + + type rec2_t is record + field2 : natural; + end record; + + type rec1_t is record + field1 : rec2_t; + end record; + + constant rec_val : rec1_t := (field1 => (field2 => 0)); + alias rec_alias is rec_val; + + -- Good + constant a : rec2_t := rec_val.field1; + constant b : natural := rec_val.field1.field2; + constant c : rec2_t := rec_alias.field1; + constant d : natural := rec_alias.field1.field2; + + -- Bad + constant e : natural := rec_val.missing; + constant f : natural := rec_val.field1.missing; + constant g : natural := rec_alias.missing; + constant h : natural := rec_alias.field1.missing; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("missing", 1), + "No declaration of 'missing' within record type 'rec1_t'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("missing", 2), + "No declaration of 'missing' within record type 'rec2_t'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("missing", 3), + "No declaration of 'missing' within record type 'rec1_t'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("missing", 4), + "No declaration of 'missing' within record type 'rec2_t'", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn find_all_references_of_record_field() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type rec_t is record + field : natural; + end record; + + signal sig : rec_t; +begin + sig.field <= 1; +end architecture;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let references = vec![code.s("field", 1).pos(), code.s("field", 2).pos()]; + + assert_eq_unordered( + &root.find_all_references_pos(&code.s1("field").pos()), + &references, + ); +} + +#[test] +fn record_subtype_can_be_selected() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + type rec_t is record + elem : integer_vector; + end record; + + subtype sub_t is rec_t(elem(0 to 1)); + + constant const1 : sub_t := (elem => (0, 1)); + + -- Ok + constant const2 : integer := const1.elem(0); + + -- Not ok + constant const3 : integer := const1.missing; + +end package body; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("missing", 1), + "No declaration of 'missing' within record type 'rec_t'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn acccess_type_of_record_can_be_selected() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end; + +architecture a of ent is + type rec_t is record + elem : integer; + end record; + + type rec_access_t is access rec_t; +begin + + main : process + variable avar : rec_access_t := new rec_t'(elem => 0); + variable v : integer; + begin + -- Ok + v := avar.elem; + -- Not ok + v := avar.missing; + end process; + +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("missing", 1), + "No declaration of 'missing' within record type 'rec_t'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn protected_type_can_be_selected() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end; + +architecture a of ent is + type prot_t is protected + function foo return natural; + end protected; + + type prot_t is protected body + function foo return natural is + begin + return 0; + end; + end protected body; + + shared variable pvar : prot_t; +begin + + main : process + variable v : natural; + begin + -- Ok + v := pvar.foo; + + -- Not ok + v := pvar.missing; + end process; + +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("missing", 1), + "No declaration of 'missing' within protected type 'prot_t'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn incomplete_access_type_of_record_can_be_selected() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end; + +architecture a of ent is + + type rec_t; + type rec_access_t is access rec_t; + + type rec_t is record + elem : natural; + child : rec_access_t; + end record; + +begin + + main : process + variable rvar : rec_t := (elem => 0, child => null); + variable v : natural; + begin + -- Ok + v := rvar.elem; + v := rvar.child.elem; + -- Not ok + v := rvar.missing; + v := rvar.child.missing; + end process; + +end architecture; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("missing", 1), + "No declaration of 'missing' within record type 'rec_t'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("missing", 2), + "No declaration of 'missing' within record type 'rec_t'", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn hover_for_physical_type_units() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type time_t is range -9223372036854775807 to 9223372036854775807 +units + small; + big = 1000 small; +end units; + +constant the_time1 : time_t := small; +constant the_time2 : time_t := 1000 small; +constant the_time3 : time_t := big; +constant the_time4 : time_t := 1000 big; + +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let time_t = root + .search_reference(code.source(), code.s1("time_t").start()) + .unwrap(); + assert_eq!(time_t.decl_pos().unwrap(), code.s1("time_t").pos().as_ref()); + assert_eq!( + root.find_all_references(time_t), + vec![ + code.s("time_t", 1).pos(), + code.s("time_t", 2).pos(), + code.s("time_t", 3).pos(), + code.s("time_t", 4).pos(), + code.s("time_t", 5).pos(), + ] + ); + + for i in 0..3 { + let ent = root + .search_reference(code.source(), code.s("small", 1 + i).start()) + .unwrap(); + assert_eq!(ent.decl_pos().unwrap(), &code.s1("small").pos()); + assert_eq!(root.format_declaration(ent), Some("small".to_string())); + } + + for i in 0..2 { + let ent = root + .search_reference(code.source(), code.s("big", 1 + i).start()) + .unwrap(); + assert_eq!(ent.decl_pos().unwrap(), &code.s1("big").pos()); + assert_eq!(root.format_declaration(ent), Some("1000 small".to_string())); + } +} + +#[test] +fn resolve_record_aggregate_choices() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : natural; +end rec_t; + +constant good : rec_t := (field => 0); +constant bad : rec_t := (missing => 0); +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing' within record type 'rec_t'", + ErrorCode::Unresolved, + )], + ); + let field = root + .search_reference(code.source(), code.s("field", 2).start()) + .unwrap(); + assert_eq!(field.decl_pos().unwrap(), code.s1("field").pos().as_ref()); + assert_eq!( + root.find_all_references(field), + vec![code.s("field", 1).pos(), code.s("field", 2).pos(),] + ); + + assert_eq!( + root.format_declaration(field), + Some("field : natural".to_string()) + ); +} + +#[test] +fn unary_operator() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant i0 : integer := 0; +constant good1 : integer := - i0; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + let integer = root.find_standard_symbol("INTEGER"); + + let op_pos = code.s1("-"); + let minus = root + .search_reference(code.source(), op_pos.start()) + .unwrap(); + assert_eq!(minus.decl_pos(), integer.decl_pos()); + + check_no_diagnostics(&diagnostics); +} + +#[test] +fn binary_operator() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant i0 : integer := 0; +constant good1 : integer := i0 + i0; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + let integer = root.find_standard_symbol("INTEGER"); + + let op_pos = code.s1("+"); + let minus = root + .search_reference(code.source(), op_pos.start()) + .unwrap(); + assert_eq!(minus.decl_pos(), integer.decl_pos()); + + check_no_diagnostics(&diagnostics); +} + +#[test] +fn attribute_happy_path() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant c0 : string := \"block\"; +attribute ram_style : string; +signal ram : integer_vector(0 to 15); + +attribute ram_style of ram : signal is c0; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + { + // References in the expression + let ref_pos = code.s1("signal is c0;").s1("c0"); + let decl = root + .search_reference(code.source(), ref_pos.start()) + .unwrap(); + assert_eq!(code.s1("c0").pos(), decl.decl_pos().cloned().unwrap()); + } + { + // References to the attribute itself + let ref_pos = code.s1("attribute ram_style of").s1("ram_style"); + let decl = root + .search_reference(code.source(), ref_pos.start()) + .unwrap(); + assert_eq!( + code.s1("attribute ram_style").s1("ram_style").pos(), + decl.decl_pos().cloned().unwrap() + ); + } + + { + // References to the named entity + let ref_pos = code.s1("of ram").s1("ram"); + let decl = root + .search_reference(code.source(), ref_pos.start()) + .unwrap(); + assert_eq!( + code.s1("signal ram").s1("ram").pos(), + decl.decl_pos().cloned().unwrap() + ); + } +} + +#[test] +fn attribute_missing_names() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant c0 : string := \"block\"; +attribute ram_style : string; +signal ram : integer_vector(0 to 15); + +attribute missing1 of ram : signal is c0; +attribute ram_style of missing2 : signal is c0; +attribute ram_style of ram : signal is missing3; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("missing1"), + "No declaration of 'missing1'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("missing2"), + "No declaration of 'missing2'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("missing3"), + "No declaration of 'missing3'", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn attribute_spec_with_non_attribute() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant bad : natural := 0; +signal ram : integer_vector(0 to 15); +attribute bad of ram : signal is 0; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s1("attribute bad").s1("bad"), + "constant 'bad' is not an attribute", + )], + ); +} + +#[test] +fn selected_function_is_resolved() { + // This test case exists because a bug was found + // where a the arguments of a selected name prefix + // were not resolved because the 'myfun' name already had a referenc set + // which cause the disambiguation not to run + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + function myfun(arg : integer) return boolean; +end package; + +entity ent is +end entity; + +architecture a of ent is + constant c0 : integer := 0; + constant c1 : boolean := work.pkg.myfun(c0); +begin +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert!(root + .search_reference(code.source(), code.s1("myfun(c0)").s1("c0").start()) + .is_some()); +} + +#[test] +fn subpgm_references_includes_both_definition_and_declaration() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " + package pkg is + function myfun(arg : integer) return integer; +end package; + +package body pkg is + function myfun(arg : integer) return integer is + begin + return 0; + end; +end package body; + +entity ent is + +end entity; + +architecture a of ent is + constant c0 : natural := work.pkg.myfun(0); +begin +end; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let decl = root + .search_reference(code.source(), code.sa("work.pkg.", "myfun").start()) + .unwrap(); + + assert_eq!( + root.find_all_references(decl), + vec![ + code.s("myfun", 1).pos(), + code.s("myfun", 2).pos(), + code.s("myfun", 3).pos() + ] + ); + + assert_eq!( + root.find_definition_of( + root.search_reference(code.source(), code.s1("myfun").start()) + .unwrap() + ) + .unwrap() + .decl_pos(), + Some(&code.s("myfun", 2).pos()) + ); +} + +#[test] +fn find_all_references_of_deferred_constant() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + constant c0 : natural; +end package; + + +package body pkg is + constant c0 : natural := 0; +end package body; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let references = vec![code.s("c0", 1).pos(), code.s("c0", 2).pos()]; + + assert_eq_unordered( + &root.find_all_references_pos(&code.s("c0", 1).pos()), + &references, + ); + + assert_eq_unordered( + &root.find_all_references_pos(&code.s("c0", 2).pos()), + &references, + ); + + assert_eq!( + root.find_definition_of( + root.search_reference(code.source(), code.s1("c0").start()) + .unwrap() + ) + .unwrap() + .decl_pos(), + Some(&code.s("c0", 2).pos()) + ); +} + +#[test] +fn find_architecture_references() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent1 is +end entity; + +architecture a1 of ent1 is +begin +end architecture; + +architecture a2 of ent1 is +begin +end architecture; + +entity ent2 is +end entity; + +architecture a of ent2 is +begin + good_inst1 : entity work.ent1(a1); + good_inst2 : entity work.ent1(a2); + bad_inst : entity work.ent1(a3); +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.sa("work.ent1(", "a3"), + "No architecture 'a3' for entity 'libname.ent1'", + ErrorCode::Unresolved, + )], + ); + + assert_eq_unordered( + &root.find_all_references_pos(&code.s("a1", 1).pos()), + &[code.s("a1", 1).pos(), code.s("a1", 2).pos()], + ); + + assert_eq_unordered( + &root.find_all_references_pos(&code.s("a1", 2).pos()), + &[code.s("a1", 1).pos(), code.s("a1", 2).pos()], + ); + + assert_eq!( + root.find_definition_of( + root.search_reference(code.source(), code.s("a2", 2).start()) + .unwrap() + ) + .unwrap() + .decl_pos(), + Some(&code.s("a2", 1).pos()) + ); +} + +#[test] +fn find_end_identifier_references_of_declarations() { + for name in [ + "ent1", "a1", "rec_t", "prot_t", "phys_t", "fun1", "proc1", "comp1", "pkg", "cfg1", "ctx1", + ] { + check_search_reference_with_name( + name, + " +entity ent1 is +end entity ent1; + +architecture a1 of ent1 is + + type rec_t is record + field: natural; + end record rec_t; + + type prot_t is protected + end protected prot_t; + + type prot_t is protected body + end protected body prot_t; + + type phys_t is range 0 to 10 + units + bangs; + bugs = 10 bangs; + end units phys_t; + + function fun1 return integer is + begin + end function fun1; + + procedure proc1 is + begin + end procedure proc1; + + component comp1 is + end component comp1; +begin +end architecture a1; + +package pkg is +end package pkg; + +package body pkg is +end package body pkg; + +configuration cfg1 of ent1 is + for rtl(0) + end for; +end configuration cfg1; + +context ctx1 is +end context ctx1; + ", + ); + } +} + +#[test] +fn find_end_identifier_references_of_concurrent() { + for name in ["b1", "p1", "fg1", "ig1", "ialt1", "cg1", "cgalt1"] { + check_search_reference_with_name( + name, + " +entity ent1 is +end entity ent1; + +architecture a1 of ent1 is +begin + b1: block + begin + end block b1; + + p1: process + begin + end process p1; + + fg1: for i in 0 to 10 generate + end generate fg1; + + ig1: if true generate + else ialt1: generate + end ialt1; + end generate ig1; + + cg1: case 0 generate + when cgalt1: 0 => + assert false; + end cgalt1; + end generate cg1; + +end architecture; + ", + ); + } +} + +#[test] +fn find_end_identifier_references_of_sequential() { + for name in ["if0", "loop0", "c0"] { + check_search_reference_with_name( + name, + " +entity ent1 is +end entity ent1; + +architecture a1 of ent1 is +begin + process + begin + if0: if true then + end if if0; + + loop0: for i in 0 to 1 loop + next loop0; + exit loop0; + end loop loop0; + + c0: case 0 is + when others => + end case c0; + end process; +end architecture; + ", + ); + } +} + +/// This is a regression test for github issue #229 +/// The reference was not set on the subprogram name when selected +#[test] +fn sets_reference_on_selected_subprogram_call() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + type my_record_t is record + field1 : natural; + end record my_record_t; + + function my_function return my_record_t is + begin + return (others => 0); + end function; + + constant CONST1 : natural := my_function.field1; +begin +end architecture; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + assert_eq!(root.find_all_unresolved().1, vec![]); +} + +#[test] +pub fn parse_selected_all() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +type t_str_ptr is access string; +signal str_value_ptr : t_str_ptr; +signal v_value : str_value_ptr.all'subtype; + ", + ); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + let pos = code.s1("v_value").pos(); + let value = root + .search_reference(&pos.source, pos.start()) + .expect("Signal has no reference"); + let typ = match value.kind() { + AnyEntKind::Object(o) => o.subtype.type_mark(), + _ => panic!("Expecting object"), + }; + assert_eq!(typ.id, root.standard_types.as_ref().unwrap().string) +} + +#[test] +pub fn select_package_from_other_package() { + let mut builder = LibraryBuilder::new(); + let _code = builder.code( + "libname", + "package pkg1 is + generic(i_gen: natural); + + type type1_t is array(natural range <>) of bit; +end package pkg1; + +package pkg2 is + package pkg1 is new work.pkg1 generic map (i_gen => 5); +end package pkg2; + +package pkg3 is + variable v1 : work.pkg2.pkg1.type1_t; +end package;", + ); + let (_root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); +} + +#[test] +pub fn select_package_from_instantiated_package() { + let mut builder = LibraryBuilder::new(); + let _code = builder.code( + "libname", + "package pkg1 is + generic(i_gen: natural); + + type type1_t is array(natural range <>) of bit; +end package pkg1; + +package pkg2 is + generic (i_gen : natural); + + package pkg1 is new work.pkg1 generic map (i_gen); +end package pkg2; + +package pkg3 is + package pkg2 is new work.pkg2 generic map (1); + variable v1 : pkg2.pkg1.type1_t; +end package;", + ); + let (_root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); +} + +#[test] +pub fn resolve_selected_names_of_types() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + "\ +package foo is + type test_t is record + asdf: bit_vector(10 downto 0); + end record; + + signal a: integer := test_t.asdf'high; + signal b: bit_vector(test_t.asdf'high downto test_t.asdf'low); + signal c: bit_vector(test_t.asdf'range); + signal d: integer := test_t.asdf'high; + signal e: bit_vector(test_t.asdf'range); +end package; + ", + ); + check_no_diagnostics(&builder.analyze()) +} diff --git a/vhdl_lang/src/analysis/tests/resolves_type_mark.rs b/vhdl_lang/src/analysis/tests/resolves_type_mark.rs new file mode 100644 index 0000000..e27b3cf --- /dev/null +++ b/vhdl_lang/src/analysis/tests/resolves_type_mark.rs @@ -0,0 +1,513 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use pretty_assertions::assert_eq; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn resolves_type_mark_in_subtype_indications() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is +-- Object declaration +constant const : natural := 0; +constant const2 : missing := 0; + +-- File declaration +file fil : std.textio.text; +file fil2 : missing; + +-- Alias declaration +alias foo : natural is const; +alias foo2 : missing is const; + +-- Array type definiton +type arr_t is array (natural range <>) of natural; +type arr_t2 is array (natural range <>) of missing; + +-- Access type definiton +type acc_t is access natural; +type acc_t2 is access missing; + +-- Subtype definiton +subtype sub_t is natural range 0 to 1; +subtype sub_t2 is missing range 0 to 1; + +-- Record definition +type rec_t is record + f1 : natural; + f2 : missing; +end record; + +-- Interface file +procedure p1 (fil : std.textio.text); +procedure p2 (fil : missing); + +-- Interface object +function f1 (const : natural) return natural; +function f2 (const : missing) return natural; +end package;", + ); + + let expected = (0..9) + .map(|idx| { + Diagnostic::new( + code.s("missing", 1 + idx), + "No declaration of 'missing'", + ErrorCode::Unresolved, + ) + }) + .collect(); + + let diagnostics = builder.analyze(); + check_diagnostics(diagnostics, expected); +} + +#[test] +fn resolves_return_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +function f1 (const : natural) return natural; +function f2 (const : natural) return missing; +end package;", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn resolves_attribute_declaration_type_mark() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +attribute attr : string; +attribute attr2 : missing; +end package;", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +fn search_resolved_type_mark() { + let mut builder = LibraryBuilder::new(); + let code1 = builder.code( + "libname", + " +package pkg is + type typ_t is (foo, bar); +end package;", + ); + + let code2 = builder.code( + "libname", + " +use work.pkg.all; + +package pkg2 is + constant c : typ_t := bar; +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let decl_pos = code1.s1("typ_t").pos(); + + // Cursor before symbol + assert_eq!( + root.search_reference_pos(code2.source(), code2.s1(" typ_t").start()), + None + ); + + // Cursor at beginning of symbol + assert_eq!( + root.search_reference_pos(code2.source(), code2.s1("typ_t").start()), + Some(decl_pos.clone()) + ); + + // Cursor at end of symbol + assert_eq!( + root.search_reference_pos(code2.source(), code2.s1("typ_t").end()), + Some(decl_pos) + ); + + // Cursor after end of symbol + assert_eq!( + root.search_reference_pos(code2.source(), code2.s1("typ_t ").end()), + None + ); +} + +#[test] +fn search_reference_on_declaration_returns_declaration() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type typ_t is (foo, bar); +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let decl_pos = code.s1("typ_t").pos(); + + assert_eq!( + root.search_reference_pos(code.source(), decl_pos.start()), + Some(decl_pos) + ); +} + +#[test] +fn find_all_references_of_type_mark() { + let mut builder = LibraryBuilder::new(); + let code1 = builder.code( + "libname", + " +package pkg is + type typ_t is (foo, bar); + constant c1 : typ_t := bar; +end package;", + ); + + let code2 = builder.code( + "libname", + " +use work.pkg.all; + +package pkg2 is + constant c2 : typ_t := bar; + constant c3 : typ_t := bar; +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let references = vec![ + code1.s("typ_t", 1).pos(), + code1.s("typ_t", 2).pos(), + code2.s("typ_t", 1).pos(), + code2.s("typ_t", 2).pos(), + ]; + + assert_eq_unordered( + &root.find_all_references_pos(&code1.s1("typ_t").pos()), + &references, + ); +} + +#[test] +fn find_references_in_record_defintions() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type typ_t is (foo, bar); + type rec_t is record + field : typ_t; + end record; +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + // Goto declaration from declaration + assert_eq!( + root.search_reference_pos(code.source(), code.s("typ_t", 1).end()), + Some(code.s("typ_t", 1).pos()) + ); + + // Goto declaration from reference + assert_eq!( + root.search_reference_pos(code.source(), code.s("typ_t", 2).end()), + Some(code.s("typ_t", 1).pos()) + ); + + assert_eq_unordered( + &root.find_all_references_pos(&code.s("typ_t", 1).pos()), + &[code.s("typ_t", 1).pos(), code.s("typ_t", 2).pos()], + ); +} + +#[test] +fn find_references_in_array_defintions() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + -- Dummy type to use where we do not care + type placeholder_t is (a, b); + + -- The type we want to resolve references to + type typ_t is (foo, bar); + + -- With index subtype constraint + type arr1_t is array (typ_t range <>) of typ_t; + type arr2_t is array (missing_t range <>) of placeholder_t; + + type arr3_t is array (typ_t) of typ_t; + type arr4_t is array (missing_t) of placeholder_t; + +end package;", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + let num_missing = 2; + let expected = (1..=num_missing) + .map(|idx| { + Diagnostic::new( + code.s("missing_t", idx), + "No declaration of 'missing_t'", + ErrorCode::Unresolved, + ) + }) + .collect(); + check_diagnostics(diagnostics, expected); + + let num_references = 5; + let mut references = Vec::new(); + + for i in 1..=num_references { + let refpos = code.s("typ_t", i).pos(); + assert_eq!( + root.search_reference_pos(code.source(), refpos.end()), + Some(code.s("typ_t", 1).pos()), + "i={}", + i + ); + references.push(refpos.clone()); + } + + assert_eq_unordered( + &root.find_all_references_pos(&code.s("typ_t", 1).pos()), + &references, + ); +} + +#[test] +fn search_type_mark_in_file_object_declaration() { + check_search_reference( + " +package pkg is + type decl is file of character; + file foo : decl; +end package; +", + ); +} + +#[test] +fn error_on_type_mark_with_overloaded() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + function bad return natural is + begin + end function; + + constant err : bad := 0; + +end package body; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error( + &code, + "bad", + 2, + 1, + "type", + "function bad[return NATURAL]", + )], + ); +} + +#[test] +fn error_on_type_mark_with_non_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + constant bad : natural := 0; + constant err : bad := 0; +end package body; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error(&code, "bad", 2, 1, "type", "constant 'bad'")], + ); +} + +#[test] +fn error_on_type_mark_with_alias_of_non_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + constant const : natural := 0; + alias bad is const; + constant name : bad := 0; +end package; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error( + &code, + "bad", + 2, + 1, + "type", + "alias 'bad' of constant", + )], + ); +} + +#[test] +fn test_qualified_expression_must_be_a_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant bar : natural := 0; +constant foo : natural := bar'(0); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![kind_error(&code, "bar", 2, 1, "type", "constant 'bar'")], + ); +} + +#[test] +fn test_type_mark_with_subtype_attribute_is_ok() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +signal sig0 : integer_vector(0 to 7); + +type rec_t is record + field: integer_vector(0 to 7); +end record; +signal rec : rec_t; + +attribute attr : sig0'subtype; +signal sig1 : sig0'subtype; +signal sig2 : rec.field'subtype; +", + ); + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn check_good_type_marks() { + check_code_with_no_diagnostics( + " + +package gpkg is + -- Interface type + generic (type type_t); + + type record_t is record + field : type_t; + end record; +end package; + +entity ent is +end entity ent; + +architecture arch of ent is + type incomplete; + -- Incomplete type + type access_t is access incomplete; + type incomplete is record + field : access_t; + end record; + + type ptype_t is protected + end protected; + + type ptype_t is protected body + end protected body; + + -- Protected type + shared variable ptype : ptype_t; + + -- Other type + type enum_t is (alpha, beta); + constant const : enum_t := alpha; + + -- Alias of type + alias alias_t is enum_t; + constant const2 : alias_t := alpha; +begin +end architecture; +", + ); +} + +pub fn kind_error( + code: &Code, + name: &str, + occ: usize, + occ_decl: usize, + expected: &str, + got: &str, +) -> Diagnostic { + Diagnostic::mismatched_kinds(code.s(name, occ), format!("Expected {expected}, got {got}")) + .related(code.s(name, occ_decl), "Defined here") +} diff --git a/vhdl_lang/src/analysis/tests/sensitivity_list.rs b/vhdl_lang/src/analysis/tests/sensitivity_list.rs new file mode 100644 index 0000000..6483718 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/sensitivity_list.rs @@ -0,0 +1,121 @@ +//! This Source Code Form is subject to the terms of the Mozilla Public +//! License, v. 2.0. If a copy of the MPL was not distributed with this file, +//! You can obtain one at http://mozilla.org/MPL/2.0/. +//! +//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn must_be_object_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + procedure proc(signal good : in bit) is + begin + wait on good; + wait on proc; + end; +end package body; + + ", + ); + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("wait on proc").s1("proc"), + "procedure proc[BIT] is not a signal and cannot be in a sensitivity list", + ErrorCode::DisallowedInSensitivityList, + )], + ) +} + +#[test] +fn must_be_signal_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + constant c0 : bit := '0'; + procedure proc(signal good : in bit) is + begin + wait on good; + wait on c0; + end; +end package body; + + ", + ); + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("wait on c0").s1("c0"), + "constant 'c0' is not a signal and cannot be in a sensitivity list", + ErrorCode::DisallowedInSensitivityList, + )], + ) +} + +#[test] +fn must_not_be_output() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is +end package; + +package body pkg is + procedure proc(signal bad : out bit) is + begin + wait on bad; + end; +end package body; + + ", + ); + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("wait on bad").s1("bad"), + "interface signal 'bad' of mode out cannot be in a sensitivity list", + ErrorCode::DisallowedInSensitivityList, + )], + ) +} + +#[test] +fn may_be_output_port() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +entity ent is + port (oport : out bit); +end entity; + +architecture a of ent is +begin + main: process (oport) + begin + end process main; +end architecture; + + ", + ); + let (_, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); +} diff --git a/vhdl_lang/src/analysis/tests/subprogram_arguments.rs b/vhdl_lang/src/analysis/tests/subprogram_arguments.rs new file mode 100644 index 0000000..e8babbf --- /dev/null +++ b/vhdl_lang/src/analysis/tests/subprogram_arguments.rs @@ -0,0 +1,432 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn wrong_number_of_arguments() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function subpgm(arg: natural) return natural +is begin +end; + +signal good : natural := subpgm(0); +signal bad : natural := subpgm; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("subpgm;").s1("subpgm"), + "Invalid call to 'subpgm'", + ErrorCode::InvalidCall, + ) + .related(code.s1("subpgm"), "Missing association of parameter 'arg'")], + ); +} + +#[test] +fn procedure_calls() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "lib", + " +entity ent is +end entity; + +architecture a of ent is + function subpgm(arg: natural) return natural + is begin + end; + + procedure theproc(arg: natural) + is begin + end; + + signal thesig : integer_vector(0 to 1); +begin + + subpgm(0); + theproc(0); + thesig(0); +end architecture; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("subpgm", 2), + "Invalid procedure call", + ErrorCode::InvalidCall, + ) + .related( + code.s("subpgm", 1), + "function subpgm[NATURAL return NATURAL] is not a procedure", + ), + Diagnostic::mismatched_kinds( + code.s("thesig", 2), + "signal 'thesig' of array type 'INTEGER_VECTOR' is not a procedure", + ), + ], + ); +} + +#[test] +fn resolve_overloaded_subprogram_by_return_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function subpgm(arg: natural) return character +is begin +end; + +function subpgm(arg: natural) return natural +is begin +end; + + +signal good1 : natural := subpgm(0); +signal good2 : character := subpgm(0); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 3).end()), + Some(code.s("subpgm", 2).pos()) + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 4).end()), + Some(code.s("subpgm", 1).pos()) + ); +} + +#[test] +fn resolves_formals() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function subpgm(arg1 : integer) return integer; +constant good : integer := subpgm(arg1 => 1); +constant bad : integer := subpgm(arg2 => 1); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("arg2"), + "No declaration of 'arg2'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("subpgm(arg2 => 1)"), + "No association of parameter 'arg1'", + ErrorCode::Unassociated, + ) + .related(code.s1("arg1"), "Defined here"), + ], + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("arg1", 2).end()), + Some(code.s("arg1", 1).pos()) + ); +} + +#[test] +fn resolve_overloaded_subprogram_by_argument() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function subpgm(arg: character) return natural +is begin +end; + +function subpgm(arg: natural) return natural +is begin +end; + + +signal good1 : natural := subpgm(0); +signal good2 : natural := subpgm('c'); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 3).end()), + Some(code.s("subpgm", 2).pos()) + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 4).end()), + Some(code.s("subpgm", 1).pos()) + ); +} +#[test] +fn subprogram_argument_not_associated() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function subpgm(arg1: natural; arg2: character) return natural +is begin +end; + +signal bad : natural := subpgm(0); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("subpgm(0)"), + "No association of parameter 'arg2'", + ErrorCode::Unassociated, + ) + .related(code.s1("arg2"), "Defined here")], + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 2).end()), + Some(code.s("subpgm", 1).pos()) + ); +} + +#[test] +fn subprogram_extra_argument_not_associated() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function subpgm(arg1: natural) return natural +is begin +end; + +signal bad : natural := subpgm(1111, 2222); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("2222"), + "Unexpected extra argument", + ErrorCode::TooManyArguments, + )], + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("subpgm", 2).end()), + Some(code.s("subpgm", 1).pos()) + ); +} + +#[test] +fn for_loop_indexes_no_false_positives() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: integer) is +begin +end procedure; + +procedure theproc(arg: boolean) is +begin +end procedure; + +procedure calling is +variable foo : natural; +begin +for i in 0 to 3 loop + theproc(i); +end loop; +end procedure; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("theproc(i)").start()), + Some(code.s1("theproc").pos()) + ); +} + +#[test] +fn default_before_positional_disambiguation() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: integer) is +begin +end procedure; + +procedure theproc(arg: integer := 0; arg2: boolean) is +begin +end procedure; + +procedure calling is +begin + theproc(0); +end procedure; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("theproc(0)").start()), + Some(code.s1("theproc").pos()) + ); +} + +#[test] +fn named_argument_before_positional() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg1: integer; arg2 : natural) is +begin +end procedure; + +procedure calling is +begin + theproc(arg1 => 0, 0); +end procedure; +", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("arg1", 2), + "Named arguments are not allowed before positional arguments", + ErrorCode::NamedBeforePositional, + )], + ); +} + +#[test] +fn argument_associated_as_both_named_and_positional() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: integer) is +begin +end procedure; + +procedure calling is +begin + theproc(0, arg => 0); +end procedure; +", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("arg", 2), + "parameter 'arg' has already been associated", + ErrorCode::AlreadyAssociated, + ) + .related(code.s1("theproc(0, ").s1("0"), "Previously associated here")], + ); +} + +#[test] +fn duplicate_named_argument() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: integer) is +begin +end procedure; + +procedure calling is +begin + theproc(arg => 0, arg => 0); +end procedure; +", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("arg", 3), + "parameter 'arg' has already been associated", + ErrorCode::AlreadyAssociated, + ) + .related(code.s("arg", 2), "Previously associated here")], + ); +} + +#[test] +fn partial_named_argument_is_allowed_multiple_times() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +procedure theproc(arg: bit_vector) is +begin +end procedure; + +procedure calling is +begin + theproc(arg(0) => '0', arg(1 to 2) => \"01\"); +end procedure; +", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn cannot_combine_partial_and_full_argument() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: bit_vector) is +begin +end procedure; + +procedure calling is +begin + theproc(arg(0) => '0', arg => \"01\"); +end procedure; +", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("arg", 3), + "parameter 'arg' has already been associated", + ErrorCode::AlreadyAssociated, + ) + .related(code.s1("arg(0)"), "Previously associated here")], + ); +} diff --git a/vhdl_lang/src/analysis/tests/subprogram_instance.rs b/vhdl_lang/src/analysis/tests/subprogram_instance.rs new file mode 100644 index 0000000..cbc1d14 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/subprogram_instance.rs @@ -0,0 +1,558 @@ +use vhdl_lang::data::error_codes::ErrorCode; +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; +use crate::syntax::test::check_diagnostics; +use crate::Diagnostic; + +#[test] +pub fn cannot_instantiate_procedure_that_does_not_exist() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure proc + generic ( x: natural := 1 ) is +begin +end proc; + +procedure proc is new foo; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("foo").pos(), + "No declaration of 'foo'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +pub fn instantiate_wrong_type() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +signal x : bit; + +function proc is new x; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::mismatched_kinds( + code.s1("new x").s1("x"), + "signal 'x' does not denote an uninstantiated subprogram", + )], + ); +} + +#[test] +pub fn ambiguous_multiple_uninstantiated_subprograms() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure foo + generic (type T) + parameter (x : bit) +is begin +end foo; + +procedure foo + generic (type T) + parameter (x : bit; y: bit) +is begin +end foo; + +procedure proc is new foo; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("new foo").s1("foo"), + "Ambiguous instantiation of 'foo'", + ErrorCode::AmbiguousInstantiation, + ) + .related(code.s("foo", 1), "Might be procedure foo[BIT]") + .related(code.s("foo", 3), "Might be procedure foo[BIT, BIT]")], + ) +} + +#[test] +pub fn by_signature_resolved_multiple_uninstantiated_subprograms() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure foo + generic (a: natural) + parameter (x : bit) +is begin +end foo; + +procedure foo + generic (a: natural) + parameter (x : bit; y: bit) +is begin +end foo; + +procedure proc is new foo [bit] generic map (a => 5); +procedure proc2 is new foo [bit, bit] generic map (a => 5); + ", + ); + + check_no_diagnostics(&builder.analyze()); +} + +#[test] +pub fn complain_on_mismatching_signature() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure foo + generic (type T) + parameter (x : bit) +is begin +end foo; + +procedure proc is new foo [bit, bit]; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("[bit, bit]").pos(), + "Signature does not match the the signature of procedure foo[BIT]", + ErrorCode::SignatureMismatch, + )], + ); +} + +#[test] +pub fn can_instantiate_procedure_that_exists() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure proc + generic ( x: natural := 1 ) is +begin +end proc; + +procedure proc is new proc; + ", + ); + + check_no_diagnostics(&builder.analyze()); +} + +#[test] +pub fn instantiated_kind_vs_declared_kind() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure prok + generic ( x: natural := 1 ) is +begin +end prok; + +function func is new prok; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("function"), + "Instantiating procedure as function", + ErrorCode::MismatchedSubprogramInstantiation, + ) + .related(code.s1("prok"), "procedure prok[] declared here")], + ); + + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function funk + generic ( x: natural := 1 ) return bit is +begin +end funk; + +procedure proc is new funk; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("procedure"), + "Instantiating function as procedure", + ErrorCode::MismatchedSubprogramInstantiation, + ) + .related(code.s1("funk"), "function funk[return BIT] declared here")], + ); + + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +function proc generic (type T) return bit is +begin +end proc; + +function proc is new proc; + ", + ); + + check_no_diagnostics(&builder.analyze()); + + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure proc generic (type T) is +begin +end proc; + +procedure proc is new proc; + ", + ); + + check_no_diagnostics(&builder.analyze()) +} + +#[test] +pub fn cannot_instantiate_procedure_without_header() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +procedure proc is +begin +end proc; + +procedure proc is new proc; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::mismatched_kinds( + code.s1("procedure proc is new").s("proc", 2).pos(), + "procedure proc[] does not denote an uninstantiated subprogram", + )], + ); +} + +#[test] +pub fn cannot_call_procedure_with_header() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity ent is +end ent; + +architecture arch of ent is + procedure proc + generic ( x: natural := 1 ) + is + begin + end proc; +begin + proc; +end architecture arch; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("begin\n proc;").s1("proc").pos(), + "uninstantiated procedure proc[] cannot be called", + ErrorCode::InvalidCall, + )], + ) +} + +#[test] +pub fn resolves_the_correct_instantiated_subprogram() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + "\ +entity ent is +end ent; + +architecture arch of ent is + procedure proc + generic ( type T ) + is + begin + end proc; + + procedure proc is new proc generic map (T => natural); +begin + proc; +end architecture arch; + ", + ); + + check_no_diagnostics(&builder.analyze()) +} + +#[test] +pub fn resolves_its_generic_map() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity ent is +end ent; + +architecture arch of ent is + procedure foo + generic ( type T ) + parameter (param : T) + is + begin + end foo; + + procedure foo is new foo generic map (T => natural); + procedure foo is new foo generic map (T => bit); +begin + foo('1'); + foo(42); +end architecture arch; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo('1')").s1("foo").end(),), + Some( + code.s1("procedure foo is new foo generic map (T => bit)") + .s1("foo") + .pos() + ) + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo(42)").s1("foo").end(),), + Some( + code.s1("procedure foo is new foo generic map (T => natural)") + .s1("foo") + .pos() + ) + ); + assert_eq!( + root.search_reference_pos( + code.source(), + code.s1("procedure foo is new foo generic map (T => natural)") + .s("foo", 2) + .end(), + ), + Some(code.s1("procedure foo").s1("foo").pos()) + ); + assert_eq!( + root.search_reference_pos( + code.source(), + code.s1("procedure foo is new foo generic map (T => bit)") + .s("foo", 2) + .end(), + ), + Some(code.s1("procedure foo").s1("foo").pos()) + ); +} + +#[test] +pub fn resolve_instantiated_function() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function foo + generic (type F) + parameter (x: F) +return F +is begin + return x; +end foo; + +function foo is new foo generic map (F => bit); + +constant x : bit := foo('1'); + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + check_no_diagnostics(&diagnostics); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo('1')").s1("foo").end(),), + Some( + code.s1("function foo is new foo generic map (F => bit);") + .s1("foo") + .pos() + ) + ); +} + +#[test] +pub fn generic_function_declaration_with_separate_body() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +function foo generic (type F) parameter (x: bit) return bit; + +function foo generic (type F) parameter (x: bit) return bit +is +begin + return x; +end foo; + +function foo is new foo generic map (F => natural); + ", + ); + + check_no_diagnostics(&builder.analyze()); +} + +// LRM 4.2.1: "An uninstantiated subprogram shall not be called, +// except as a recursive call within the body of the uninstantiated subprogram" +#[test] +#[ignore] +pub fn generic_subprogram_can_be_called_recursively() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function foo + generic (type F) + parameter (x: F) +return F +is begin + return foo(x); +end foo; + +procedure bar + generic (type F) + parameter (x: F) +is begin + bar(x); +end bar; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + + check_no_diagnostics(&diagnostics); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("foo(x)").s1("foo").end(),), + Some(code.s1("function foo").s1("foo").pos()) + ); + assert_eq!( + root.search_reference_pos(code.source(), code.s1("bar(x)").s1("bar").end(),), + Some(code.s1("procedure bar").s1("bar").pos()) + ); +} + +// LRM 4.2.1: "an uninstantiated subprogram shall not be used as a resolution +// function or used as a conversion function in an association list." +#[test] +#[ignore] +pub fn generic_subprogram_cannot_be_used_as_resolution_function() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ +function resolved generic (type F) parameter (x: F) return F; + +subtype x is resolved bit; + ", + ); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("subtype x is resolved bit").s1("resolved"), + "uninstantiated function resolved[F] return F cannot be used as resolution function", + ErrorCode::MismatchedKinds, + )], + ); +} + +#[test] +#[ignore] +pub fn generic_subprogram_cannot_be_used_as_conversion_function() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure bar (x : in bit) is +begin +end bar; + +function foo generic (type F) parameter (x: bit) return bit; + ", + ); + + let code = builder.snippet("bar(foo('1'))"); + + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("foo"), + "uninstantiated function foo[F] return F cannot be used as conversion", + ErrorCode::MismatchedKinds, + )], + ); +} + +#[test] +#[ignore] +pub fn generic_function_declaration_with_separate_body_and_type_parameters() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +function foo generic (type F) parameter (x: F) return F; +function foo generic (type F) parameter (x: F) return F +is +begin + return x; +end foo; + +function foo is new foo generic map (F => std_logic); + ", + ); + + check_no_diagnostics(&builder.analyze()); +} + +#[test] +#[ignore] +pub fn by_signature_resolved_multiple_uninstantiated_subprograms_with_generics() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + "\ +procedure foo + generic (type T) + parameter (x : T) +is begin +end foo; + +procedure foo + generic (type T) + parameter (x : T; y: T) +is begin +end foo; + +procedure proc is new foo [bit] generic map (T => bit); +procedure proc2 is new foo [bit, bit] generic map (T => bit); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} diff --git a/vhdl_lang/src/analysis/tests/tool_directive.rs b/vhdl_lang/src/analysis/tests/tool_directive.rs new file mode 100644 index 0000000..3e3c606 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/tool_directive.rs @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; + +#[test] +fn simple_tool_directive() { + let mut builder = LibraryBuilder::new(); + builder.code("libname", "`protect begin"); + check_no_diagnostics(&builder.analyze()); +} + +#[test] +fn tool_directive() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + "\ +entity my_ent is +end my_ent; + +`protect begin_protected +`protect version = 1 +`protect encrypt_agent = \"XILINX\" +`protect encrypt_agent_info = \"Xilinx Encryption Tool 2020.2\" +`protect key_keyowner = \"Cadence Design Systems.\", key_keyname = \"cds_rsa_key\", key_method = \"rsa\" +`protect encoding = (enctype = \"BASE64\", line_length = 76, bytes = 64) + ", + ); + check_no_diagnostics(&builder.analyze()); +} diff --git a/vhdl_lang/src/analysis/tests/typecheck_expression.rs b/vhdl_lang/src/analysis/tests/typecheck_expression.rs new file mode 100644 index 0000000..46f50fa --- /dev/null +++ b/vhdl_lang/src/analysis/tests/typecheck_expression.rs @@ -0,0 +1,1996 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use std::vec; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn test_integer_literal_expression_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant good_a : integer := 1; +constant good_b : natural := 2; + +constant bad_a : boolean := 3; + +subtype my_bool is boolean; +constant bad_b : my_bool := 4; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("3"), + "integer literal does not match type 'BOOLEAN'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("4"), + "integer literal does not match subtype 'my_bool'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn test_integer_literal_with_alias() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +alias alias_t is integer; +constant good : alias_t := 1; +constant bad : alias_t := false; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("false"), + "'false' does not match alias 'alias_t'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn test_character_literal_expression() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant good : character := 'a'; +constant bad : natural := 'b'; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("'b'"), + "character literal does not match subtype 'NATURAL'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn test_character_literal_expression_not_part_of_enum() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type enum_t is ('a', 'b'); +constant good : enum_t := 'a'; +constant bad : enum_t := 'c'; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("'c'"), + "character literal does not match type 'enum_t'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn test_physical_literal_expression_typecheck() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +constant good_a : time := ns; +constant good_b : time := 2 ps; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn test_string_literal_expression() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +signal good : string(1 to 3) := \"101\"; +signal bad : natural := \"110\"; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("\"110\""), + "string literal does not match subtype 'NATURAL'", + ErrorCode::TypeMismatch, + )], + ) +} + +#[test] +fn test_string_literals_allowed_characters_for_array() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type enum_t is ('a', foo); +type enum_vec_t is array (natural range <>) of enum_t; + +signal good1 : bit_vector(1 to 1) := \"1\"; +signal good2 : enum_vec_t(1 to 1) := \"a\"; + +signal bad2 : bit_vector(1 to 1) := \"2\"; +signal bad3 : enum_vec_t(1 to 1) := \"b\"; + +type enum_vec2_t is array (natural range <>, natural range <>) of enum_t; +type enum_vec3_t is array (natural range <>) of enum_vec_t(1 to 1); +signal bad4 : enum_vec2_t(1 to 1, 1 to 1) := \"a\"; +signal bad5 : enum_vec3_t(1 to 1) := \"a\"; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("\"2\""), + "type 'BIT' does not define character '2'", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s1("\"b\""), + "type 'enum_t' does not define character 'b'", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s("\"a\"", 2), + "string literal does not match array type 'enum_vec2_t'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s("\"a\"", 3), + "string literal does not match array type 'enum_vec3_t'", + ErrorCode::TypeMismatch, + ), + ], + ) +} + +#[test] +fn test_illegal_bit_strings() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant a: bit_vector := D\"1AFFE\"; +constant c: bit_vector := 8SX\"0FF\"; +constant d: bit_vector := X\"G\"; +constant e: bit_vector := X\"F\"; -- this is Ok +constant f: bit_vector := 2SX\"\"; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("D\"1AFFE\""), + "Illegal digit 'A' for base 10", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s1("8SX\"0FF\""), + "Truncating vector to length 8 would lose information", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s1("X\"G\""), + "type 'BIT' does not define character 'G'", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s1("2SX\"\""), + "Cannot expand an empty signed bit string", + ErrorCode::InvalidLiteral, + ), + ], + ) +} + +#[test] +fn test_integer_selected_name_expression_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant ival : integer := 999; + +type rec_t is record + elem : natural; +end record; + +constant rval : rec_t := (elem => 0); + +constant good_a : integer := ival; +constant good_b : natural := rval.elem; + +constant bad_a : boolean := ival; + +subtype my_bool is boolean; +constant bad_b : my_bool := rval.elem; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("ival", 3), + "constant 'ival' of integer type 'INTEGER' does not match type 'BOOLEAN'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s("rval.elem", 2), + "subtype 'NATURAL' does not match subtype 'my_bool'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn test_enum_literal_expression_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +subtype my_bool is boolean; +constant good_a : boolean := true; +constant good_b : my_bool := false; + +constant bad_a : integer := true; +constant bad_b : character := false; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("true", 2), + "'true' does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s("false", 2), + "'false' does not match type 'CHARACTER'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn test_unique_function_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function fun1 return natural is +begin + return 0; +end function; + +function fun1 return boolean is +begin + return false; +end function; + +constant good : integer := fun1; +constant bad : character := fun1; + ", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("fun1", 4), + "Could not resolve 'fun1'", + ErrorCode::Unresolved, + ) + .related( + code.s("fun1", 1), + "Does not match return type of function fun1[return NATURAL]", + ) + .related( + code.s("fun1", 2), + "Does not match return type of function fun1[return BOOLEAN]", + )], + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1(":= fun1").end()), + Some(code.s1("fun1").pos()) + ); +} + +#[test] +fn test_unique_function_default_arg_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function fun1(arg : natural := 0) return natural is +begin + return 0; +end function; + +function fun1 return boolean is +begin + return false; +end function; + +constant good : integer := fun1; +constant bad : character := fun1; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("fun1", 4), + "Could not resolve 'fun1'", + ErrorCode::Unresolved, + ) + .related( + code.s("fun1", 1), + "Does not match return type of function fun1[NATURAL return NATURAL]", + ) + .related( + code.s("fun1", 2), + "Does not match return type of function fun1[return BOOLEAN]", + )], + ); +} + +#[test] +fn test_ambiguous_function_default_arg_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function fun1(arg : natural := 0) return natural is +begin + return 0; +end function; + +function fun1(arg : boolean := false) return natural is +begin + return 0; +end function; + +-- Do not consider this as ambiguous +function fun1(arg : character) return natural is +begin + return 0; +end function; + +constant bad: integer := fun1; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1(":= fun1").s1("fun1"), + "Ambiguous call to 'fun1'", + ErrorCode::AmbiguousCall, + ) + .related( + code.s("fun1", 1), + "Might be function fun1[NATURAL return NATURAL]", + ) + .related( + code.s("fun1", 2), + "Might be function fun1[BOOLEAN return NATURAL]", + )], + ); +} + +#[test] +fn test_name_can_be_indexed() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant foo : natural := 0; +constant bar : natural := foo(0); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s("foo", 2), + "constant 'foo' of subtype 'NATURAL' cannot be indexed", + )], + ); +} + +#[test] +fn test_name_can_be_sliced() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant foo : natural := 0; +constant bar : natural := foo(0 to 0); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::mismatched_kinds( + code.s("foo", 2), + "constant 'foo' of subtype 'NATURAL' cannot be sliced", + )], + ); +} + +#[test] +fn test_access_type_can_be_indexed() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +type arr_t is array (0 to 1) of natural; +type access_t is access arr_t; + +procedure proc is + variable myvar : access_t; + variable foo : natural; +begin + foo := myvar(0); +end procedure; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn function_result_can_be_indexed() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +function thefun(arg : natural) return integer_vector is +begin + return (0, 1); +end; + +constant good : natural := thefun(0)(0); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[ignore = "Does not work yet"] +#[test] +fn function_result_can_be_indexed_no_arg() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +function thefun return integer_vector is +begin + return (0, 1); +end; + +constant good : natural := thefun(0); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn test_type_conversion() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " + constant foo : natural := natural(0); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn test_indexed_array_dimension_check() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type arr1_t is array (0 to 1) of natural; +type arr2_t is array (0 to 1, 0 to 1) of natural; +constant foo1 : arr1_t := (0, 1); +constant foo2 : arr2_t := ((0, 1), (2, 3)); + +constant bar1 : natural := foo1(0); +constant bar2 : natural := foo1(0, 1); +constant bar3 : natural := foo2(0); +constant bar4 : natural := foo2(0, 1); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("foo1(0, 1)", 1), + "Number of indexes does not match array dimension", + ErrorCode::DimensionMismatch, + ) + .related( + code.s("arr1_t", 1), + "Array type 'arr1_t' has 1 dimension, got 2 indexes", + ), + Diagnostic::new( + code.s("foo2(0)", 1), + "Number of indexes does not match array dimension", + ErrorCode::DimensionMismatch, + ) + .related( + code.s("arr2_t", 1), + "Array type 'arr2_t' has 2 dimensions, got 1 index", + ), + ], + ); +} + +#[test] +fn test_disambiguates_indexed_name_and_function_call() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant foo : natural := 0; +constant bar : natural := foo(arg => 0); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("foo", 2), + "constant 'foo' cannot be called as a function", + ErrorCode::InvalidCall, + )], + ); +} + +#[test] +fn test_typechecks_expression_for_type_mark_with_subtype_attribute() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +signal sig0 : integer_vector(0 to 7); +signal sig1 : sig0'subtype := false; +signal sig2 : sig0'subtype := (others => 0); -- ok +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("false"), + "'false' does not match array type 'INTEGER_VECTOR'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn test_typechecks_expression_for_type_mark_with_element_attribute() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +subtype ivec_subtype_t is integer_vector(0 to 7); +signal sig : integer_vector(0 to 7); + +signal bad1 : sig'element := (0, 0); +signal bad2 : sig'element := 'a'; + +signal good1 : sig'element := 0; -- ok + +subtype sub_from_object_t is sig'element; +signal good2 : sub_from_object_t := 0; -- ok + +subtype sub_from_type_t is ivec_subtype_t'element; +signal good3 : sub_from_type_t := 0; -- ok + +subtype bad1_t is good2'element; +subtype bad2_t is integer'element; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("(0, 0)"), + "composite does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'a'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::illegal_attribute( + code.s1("good2'element").s1("good2"), + "array type expected for 'element attribute", + ), + Diagnostic::illegal_attribute( + code.s1("integer'element").s1("integer"), + "array type expected for 'element attribute", + ), + ], + ); +} + +#[test] +fn qualified_expression_type_mark() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +signal good : natural := integer'(0); +signal bad1 : natural := integer'(\"hello\"); +signal bad2 : natural := string'(\"hello\"); +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("\"hello\""), + "string literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("string'(\"hello\")"), + "array type 'STRING' does not match subtype 'NATURAL'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn subprogram_positional_argument() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: character) is +begin +end procedure; + +function thefun(arg: integer) return natural is +begin + theproc(arg); + return 0; +end function; + +constant const : natural := thefun('c'); +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("theproc(arg)").s1("arg"), + "constant 'arg' of integer type 'INTEGER' does not match type 'CHARACTER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("thefun('c')").s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn check_real_vs_integer() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant rgood : real := 1.2; +constant rbad : real := 3; + +constant igood : integer := 4; +constant ibad : integer := 5.6; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("3"), + "integer literal does not match real type 'REAL'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("5.6"), + "real literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn check_bitstring_literal() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant good1 : bit_vector := x\"1\"; +constant bad1 : integer := x\"2\"; +constant bad2 : integer_vector := x\"3\"; + +type enum_t is (alpha, beta); +type arr_t is array (natural range <>) of enum_t; +constant bad3 : arr_t := x\"4\"; +constant bad4 : arr_t := x\"D\"; + +type enum0_t is ('0', alpha); +type arr0_t is array (natural range <>) of enum0_t; +constant good3 : arr0_t := x\"00\"; +constant bad5 : arr0_t := x\"6\"; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("x\"2\""), + "string literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("x\"3\""), + "string literal does not match array type 'INTEGER_VECTOR'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("x\"4\""), + "type 'enum_t' does not define character '0'", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s1("x\"D\""), + "type 'enum_t' does not define character '1'", + ErrorCode::InvalidLiteral, + ), + Diagnostic::new( + code.s1("x\"6\""), + "type 'enum0_t' does not define character '1'", + ErrorCode::InvalidLiteral, + ), + ], + ); +} + +#[test] +fn check_null_literal() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type ptr_t is access integer_vector; + +procedure proc is + variable good : ptr_t := null; + variable bad : integer := null; +begin +end procedure; + +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("null", 2), + "null literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn typecheck_aggregate() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : natural; +end record; + +constant good1 : integer_vector := (0, 1, 2); +constant good2 : rec_t := (others => 0); +constant bad1 : integer := (3, 4, 5); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("(3, 4, 5)"), + "composite does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn typecheck_multi_dimensional_array_aggregate() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type arr2_t is array (0 to 1, 2 to 3) of integer; +type arr1_t is array (0 to 1) of integer; +constant good1 : arr2_t := (others => (0, 1)); +constant good2 : arr2_t := (others => (others => 0)); +constant good3 : arr2_t := ((others => 0), (others => 0)); + +constant a1 : arr1_t := (0, 1); +constant bad1 : arr2_t := (others => 1 & 1); +constant bad2 : arr2_t := (others => a1); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("1 & 1"), + "Expected sub-aggregate for target array type 'arr2_t'", + ErrorCode::ExpectedSubAggregate, + ), + Diagnostic::new( + code.s1("=> a1").s1("a1"), + "Expected sub-aggregate for target array type 'arr2_t'", + ErrorCode::ExpectedSubAggregate, + ), + ], + ); +} + +#[test] +fn record_aggregate_must_be_simple_name() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : natural; +end record; + +constant bad1 : rec_t := (field(0) => 0); +constant bad2 : rec_t := (0 to 1 => 0); +constant bad3 : rec_t := (field | 0 => 0); + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::mismatched_kinds( + code.s1("field(0)"), + "Record aggregate choice must be a simple name", + ), + Diagnostic::mismatched_kinds( + code.s1("0 to 1"), + "Record aggregate choice must be a simple name", + ), + Diagnostic::mismatched_kinds( + code.s1("field | 0"), + "Record aggregate choice must be a simple name", + ), + ], + ); +} + +#[test] +fn record_aggregate_multiple_associations() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : natural; +end record; + +constant bad1 : rec_t := (0, field => 0); +constant bad2 : rec_t := (0, 33); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("field => 0").s1("field"), + "Record element 'field' has already been associated", + ErrorCode::AlreadyAssociated, + ) + .related(code.s1("(0, ").s1("0"), "Previously associated here"), + Diagnostic::new( + code.s1("33"), + "Unexpected positional association for record 'rec_t'", + ErrorCode::TooManyArguments, + ) + .related(code.s1("rec_t"), "Record 'rec_t' defined here"), + ], + ); +} + +#[test] +fn record_aggregate_missing_associations() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : natural; + missing: natural; +end record; + +constant bad : rec_t := (field => 0); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("(field => 0)"), + "Missing association of record element 'missing'", + ErrorCode::Unassociated, + ) + .related(code.s1("missing"), "Record element 'missing' defined here")], + ); +} + +#[test] +fn record_others() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + f1 : character; + f2 : integer; + f3 : integer; +end record; + +constant good : rec_t := (f1 => 'a', others => 0); +constant bad1 : rec_t := (f1 => 'a', others => 'c'); +constant bad2 : rec_t := (others => 0); +constant bad3 : rec_t := ('a', 0, 0, others => 3); + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("others => 'c'").s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("(others => 0)").s1("others"), + "Other elements of record 'rec_t' are not of the same type", + ErrorCode::TypeMismatch, + ) + .related(code.s1("f1"), "Element 'f1' has type 'CHARACTER'") + .related(code.s1("f2"), "Element 'f2' has integer type 'INTEGER'") + .related(code.s1("f3"), "Element 'f3' has integer type 'INTEGER'"), + Diagnostic::new( + code.s1("others => 3)").s1("others"), + "All elements of record 'rec_t' are already associated", + ErrorCode::AlreadyAssociated, + ) + .related(code.s1("rec_t"), "Record 'rec_t' defined here"), + ], + ); +} + +#[test] +fn typecheck_aggregate_element_association_expr() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : integer; +end record; + +type arr2_t is array (0 to 1, 0 to 1) of natural; + +constant good1 : integer_vector := (0, 1, 2); +constant good2 : arr2_t := ((0, 1), (2, 3)); +constant good3 : rec_t := (field => 0); +constant bad1 : integer_vector := (3, 4, 'c'); +constant bad2 : integer_vector := (others => 'd'); +constant bad3 : integer_vector := (1 to 3 => 'e'); +constant bad4 : rec_t := (field => 'f'); + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'d'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'e'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'f'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn typecheck_array_association_index() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant good : integer_vector := (0 | 1 => 11, 2 => 22); +constant bad1 : integer_vector := ('c' => 0); +constant bad2 : integer_vector := ('a' to 'z' => 0); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'a'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'z'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +/// LRM 9.3.3.3 Array aggregates +#[test] +fn array_element_association_may_be_array_of_element_type() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +constant good1 : integer_vector := (0 to 2 => (0, 1, 2)); +constant good2 : string(1 to 6) := (\"text\", others => ' '); + + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn array_element_association_may_be_type_denoting_discrete_range() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +subtype sub_t is natural range 1 to 3; +constant good : integer_vector := (sub_t => 0); + +subtype csub_t is character range 'a' to 'b'; +constant bad : integer_vector := (csub_t => 0); + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("csub_t =>").s1("csub_t"), + "subtype 'csub_t' does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn array_element_association_may_be_range() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +constant rconst : integer_vector(0 to 3) := (others => 0); +constant good1 : integer_vector := (rconst'range => 0); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn evaluates_unary_expressions() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant i0 : integer := 0; +constant r0 : real := 0.0; +constant t0 : time := 0 ns; +constant good1 : integer := - 1; +constant good2 : real := - 1.0; +constant good3 : time := - 1 ns; +constant good4 : integer := - i0; +constant good5 : real := - r0; +constant good6 : time := - t0; + +constant bad1 : character := - i0; +constant bad2 : character := - 'a'; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + + check_diagnostics( + diagnostics, + vec![ + // Prefer to complain on return type when operator arguments are unambiguous + Diagnostic::new( + code.s1("character := - i0").s1("- i0"), + "integer type 'INTEGER' does not match type 'CHARACTER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("character := - 'a'").s1("-"), + "Found no match for operator \"-\"", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn evaluates_binary_expressions() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +constant i0 : integer := 0; +constant r0 : real := 0.0; +constant t0 : time := 0 ns; +constant good1 : integer := 1 + 1; +constant good2 : real := 1.0 + 1.0; +constant good3 : time := 1 ns + 1 ns; +constant good4 : integer := i0 + i0; +constant good5 : real := r0 + r0; +constant good6 : time := t0 + t0; + +constant bad1 : character := i0 + i0; +constant bad2 : character := 'a' + 'b'; + ", + ); + + let (_, diagnostics) = builder.get_analyzed_root(); + + check_diagnostics( + diagnostics, + vec![ + // Prefer to complain on return type when operator arguments are unambiguous + Diagnostic::new( + code.s1("character := i0 + i0").s1("i0 + i0"), + "integer type 'INTEGER' does not match type 'CHARACTER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("character := 'a' + 'b'").s1("+"), + "Found no match for operator \"+\"", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn overloading_nested_ambiguous_op_has_acceptable_performance() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " + constant const : integer_vector := + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0 & + (0, 0) & 0;", + ); + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn overloading_nested_ambiguous_func_has_acceptable_performance() { + let mut builder = LibraryBuilder::new(); + builder.code( + "lib", + " +entity ent is +end entity; + +architecture a of ent is + function func(arg : integer_vector; arg2 : integer) return integer_vector is + begin + return (0, 1); + end; + + function func(arg : integer; arg2 : integer_vector) return integer_vector is + begin + return (0, 1); + end; + + function func(arg : integer_vector; arg2 : integer_vector) return integer_vector is + begin + return (0, 1); + end; + + function func(arg : integer; arg2 : integer) return integer_vector is + begin + return (0, 1); + end; + + constant const : integer_vector := + func(func( + func(func( + func(func( + func(func( + func(func( + func(func( + func(func( + func(func( + func(func( + func(func( + func((3, 3), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0), + (0, 0)), 0); +begin +end architecture;", + ); + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn attribute_spec_typecheck() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +attribute ram_style : integer; +signal good, bad : integer_vector(0 to 15); +attribute ram_style of good : signal is 0; +attribute ram_style of bad : signal is 'c'; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + )], + ); +} + +#[test] +fn attribute_spec_signature() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +attribute ram_style : integer; + +signal good_sig : integer_vector(0 to 15); +signal bad_sig : integer_vector(0 to 15); +attribute ram_style of good_sig : signal is 0; +attribute ram_style of bad_sig[return integer] : signal is 0; + +function good_fun1 return natural; +function good_fun2 return natural; +function bad_fun1 return natural; +function bad_fun1 return character; +function bad_fun2 return natural; + +attribute ram_style of good_fun1 : function is 0; +attribute ram_style of good_fun2[return natural] : function is 0; +attribute ram_style of bad_fun1 : function is 0; +attribute ram_style of bad_fun2[return boolean] : function is 0; + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("[return integer]"), + "Attribute specification should only have a signature for subprograms and enum literals", + ErrorCode::IllegalSignature + ), + Diagnostic::new( + code.s1("bad_fun1 : function").s1("bad_fun1"), + "Signature required for alias of subprogram and enum literals", + ErrorCode::SignatureRequired + ), + Diagnostic::new( + code.s1("bad_fun2[return boolean]").s1("bad_fun2"), + "Could not find declaration of 'bad_fun2' with given signature", + ErrorCode::NoOverloadedWithSignature + ).related(code.s1("bad_fun2"), "Found function bad_fun2[return NATURAL]")], + ); +} + +#[test] +fn typecheck_function_return_statement() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function good return integer is +begin + return 0; +end; + +function bad1 return integer is +begin + return 'c'; +end; + + +function bad2 return integer is +begin + return; +end; + +procedure bad3 is +begin + return 1; +end; + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'c'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("return;"), + "Functions cannot return without a value", + ErrorCode::VoidReturn, + ), + Diagnostic::new( + code.s1("return 1;"), + "Procedures cannot return a value", + ErrorCode::NonVoidReturn, + ), + ], + ); +} + +#[test] +fn typecheck_report_statement() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure wrapper is +begin + report \"good\"; + report 16#bad#; + report \"good\" severity error; + report \"good\" severity \"bad\"; +end; + + + +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("16#bad#"), + "integer literal does not match array type 'STRING'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("\"bad\""), + "string literal does not match type 'SEVERITY_LEVEL'", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn typecheck_assert_statement() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure wrapper is +begin + assert true report \"good\"; + assert true report 16#bad#; + assert true report \"good\" severity error; + assert true report \"good\" severity \"bad\"; + assert 123; + assert bit'('0'); +end; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("16#bad#"), + "integer literal does not match array type 'STRING'", + ErrorCode::TypeMismatch + ), + Diagnostic::new( + code.s1("\"bad\""), + "string literal does not match type 'SEVERITY_LEVEL'", + ErrorCode::TypeMismatch + ), + Diagnostic::new( + code.s1("123"), + "type universal_integer cannot be implicitly converted to type 'BOOLEAN'. Operator ?? is not defined for this type.", + ErrorCode::NoImplicitConversion + ), + ], + ); +} + +#[test] +fn resolves_unambiguous_boolean_reference() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure wrapper is +begin + assert true; +end; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert!(root + .search_reference(code.source(), code.s1("assert true;").s1("true").start()) + .is_some()); +} + +#[test] +fn ambiguous_boolean_conversion_favors_boolean() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +function myfun return boolean is +begin + return true; +end function; + +function myfun return bit is +begin + return '0'; +end function; + +procedure wrapper is +begin + assert myfun; +end; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let decl = root + .search_reference(code.source(), code.s1("assert myfun;").s1("myfun").start()) + .unwrap(); + + // Favors boolean + assert_eq!( + decl.decl_pos().unwrap(), + &code + .s1("function myfun return boolean is") + .s1("myfun") + .pos(), + ); +} + +#[test] +fn ambiguous_qq_conversion() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type typ1_t is (alpha, beta); +type typ2_t is (alpha, beta); +type typ3_t is (alpha, beta); + +function \"??\"(val : typ1_t) return boolean is +begin + return true; +end function; + +function \"??\"(val : typ2_t) return boolean is +begin + return true; +end function; + +procedure wrapper is +begin + assert alpha; +end; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("assert alpha").s1("alpha"), + "Ambiguous use of implicit boolean conversion ??", + ErrorCode::AmbiguousCall, + ) + .related(code.s1("typ1_t"), "Could be type 'typ1_t'") + .related(code.s1("typ2_t"), "Could be type 'typ2_t'")], + ); +} + +#[test] +fn ambiguous_qq_conversion_no_candidates() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type typ1_t is (alpha, beta); +type typ2_t is (alpha, beta); + +procedure wrapper is +begin + assert alpha; +end; +", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("assert alpha").s1("alpha"), + "Cannot disambiguate expression to type 'BOOLEAN'", + ErrorCode::AmbiguousExpression, + ) + .related( + code.s1("typ1_t"), + "Implicit boolean conversion operator ?? is not defined for type 'typ1_t'", + ) + .related( + code.s1("typ2_t"), + "Implicit boolean conversion operator ?? is not defined for type 'typ2_t'", + )], + ); +} + +#[test] +fn typecheck_scalar_constraint() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +subtype good_t is natural range 0 to 1; +subtype bad_t is character range 2 to 3; +subtype bad2_t is string range 2 to 3; + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("2"), + "integer literal does not match type 'CHARACTER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("3"), + "integer literal does not match type 'CHARACTER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("string"), + "Scalar constraint cannot be used for array type 'STRING'", + ErrorCode::IllegalConstraint, + ), + ], + ); +} + +#[test] +fn typecheck_array_index_constraint() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +subtype good_t is integer_vector(0 to 1); +subtype bad_t is integer_vector('a' to 'b'); +subtype bad2_t is integer(2 to 3); +subtype bad3_t is integer_vector(4 to 5, 6 to 7); + +type arr2d_t is array (natural range <>, natural range <>) of character; +subtype bad4_t is arr2d_t(4 to 5); + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'a'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'b'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("integer(").s1("integer"), + "Array constraint cannot be used for integer type 'INTEGER'", + ErrorCode::IllegalConstraint, + ), + Diagnostic::new( + code.s1("6 to 7"), + "Got extra index constraint for array type 'INTEGER_VECTOR'", + ErrorCode::TooManyConstraints, + ), + Diagnostic::new( + code.s1("arr2d_t(").s1("arr2d_t"), + "Too few index constraints for array type 'arr2d_t'. Got 1 but expected 2", + ErrorCode::TooFewConstraints, + ), + ], + ); +} + +#[test] +fn typecheck_array_element_constraint() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type arr_t is array (character range <>) of integer_vector; +subtype good_t is arr_t('a' to 'b')(2 to 3); +subtype good2_t is arr_t(open)(2 to 3); +subtype bad_t is arr_t('c' to 'd')('e' to 'f'); + +type sarr_t is array (character range <>) of integer; +subtype bad2_t is sarr_t('g' to 'h')('i' to 'j'); + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'e'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'f'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("('i' to 'j')"), + "Array constraint cannot be used for integer type 'INTEGER'", + ErrorCode::IllegalConstraint, + ), + ], + ); +} + +#[test] +fn typecheck_record_element_constraint() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type rec_t is record + field : integer_vector; +end record; + +subtype good_t is rec_t(field(0 to 1)); +subtype bad_t is rec_t(field('a' to 'b')); +subtype bad2_t is rec_t(missing('a' to 'b')); +subtype bad3_t is integer(bad('a' to 'b')); + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'a'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'b'"), + "character literal does not match integer type 'INTEGER'", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("missing"), + "No declaration of 'missing' within record type 'rec_t'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s1("integer(").s1("integer"), + "Record constraint cannot be used for integer type 'INTEGER'", + ErrorCode::IllegalConstraint, + ), + ], + ); +} + +#[test] +fn integer_can_be_used_as_universal_integer() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +type arr_t is array (0 to 1) of integer; +constant c0 : arr_t := (others => 0); +constant c1 : integer := c0(integer'(0)); + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn physical_type_range() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +type phys_t is range 'a' to 'b' + units + phys_unit; +end units; + +type phys2_t is range integer'(0) to integer'(0) + units + phys_unit2; +end units; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s1("'a'"), + "character literal does not match type universal_integer", + ErrorCode::TypeMismatch, + ), + Diagnostic::new( + code.s1("'b'"), + "character literal does not match type universal_integer", + ErrorCode::TypeMismatch, + ), + ], + ); +} + +#[test] +fn scalar_bit_matching_operators() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +constant good1 : bit := '0' ?= '1'; +constant good2 : bit := '0' ?/= '1'; +constant good3 : bit := '0' ?< '1'; +constant good4 : bit := '0' ?<= '1'; +constant good5 : bit := '0' ?> '1'; +constant good6 : bit := '0' ?>= '1'; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn bit_vector_matching_operators() { + let mut builder = LibraryBuilder::new(); + builder.in_declarative_region( + " +constant good1 : bit := \"01\" ?= \"10\"; +constant good2 : bit := \"01\" ?/= \"10\"; +constant good3 : bit := \"01\" ?< \"10\"; +constant good4 : bit := \"01\" ?<= \"10\"; +constant good5 : bit := \"01\" ?> \"10\"; +constant good6 : bit := \"01\" ?>= \"10\"; + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn std_ulogic_matching_operators() { + let mut builder = LibraryBuilder::new(); + builder.add_std_logic_1164(); + builder.code( + "libname", + " +library ieee; +use ieee.std_logic_1164.all; + +package pkg is + constant good1s : std_ulogic := '0' ?= '1'; + constant good2s : std_ulogic := '0' ?/= '1'; + constant good3s : std_ulogic := '0' ?< '1'; + constant good4s : std_ulogic := '0' ?<= '1'; + constant good5s : std_ulogic := '0' ?> '1'; + constant good6s : std_ulogic := '0' ?>= '1'; + + constant good1v : std_ulogic := \"10\" ?= \"10\"; + constant good2v : std_ulogic := \"10\" ?/= \"10\"; + constant good3v : std_ulogic := \"10\" ?< \"10\"; + constant good4v : std_ulogic := \"10\" ?<= \"10\"; + constant good5v : std_ulogic := \"10\" ?> \"10\"; + constant good6v : std_ulogic := \"10\" ?>= \"10\"; +end package; +", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +// Issue #317 +#[test] +fn type_mismatch_in_binary_expression() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +package foo is + function takes_slv(din : bit_vector) return boolean; + constant bar : boolean := takes_slv(true) and true; +end package;", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s1("true"), + "'true' does not match array type 'BIT_VECTOR'", + ErrorCode::TypeMismatch, + )], + ); +} diff --git a/vhdl_lang/src/analysis/tests/util.rs b/vhdl_lang/src/analysis/tests/util.rs new file mode 100644 index 0000000..19f9062 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/util.rs @@ -0,0 +1,255 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use crate::analysis::DesignRoot; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::syntax::test::*; +use crate::syntax::Symbols; +use pretty_assertions::assert_eq; +use std::collections::{hash_map::Entry, HashMap}; +use std::sync::Arc; +use vhdl_lang::VHDLStandard; + +pub struct LibraryBuilder { + code_builder: CodeBuilder, + libraries: HashMap>, +} + +impl LibraryBuilder { + pub fn new() -> LibraryBuilder { + LibraryBuilder::with_standard(VHDLStandard::default()) + } + + pub fn with_standard(standard: VHDLStandard) -> LibraryBuilder { + LibraryBuilder { + code_builder: CodeBuilder::with_standard(standard), + libraries: HashMap::default(), + } + } + + fn add_code(&mut self, library_name: &str, code: Code) { + let library_name = self.code_builder.symbol(library_name); + match self.libraries.entry(library_name) { + Entry::Occupied(mut entry) => { + entry.get_mut().push(code); + } + Entry::Vacant(entry) => { + entry.insert(vec![code]); + } + } + } + + pub fn code(&mut self, library_name: &str, code: &str) -> Code { + let code = self.code_builder.code(code); + self.add_code(library_name, code.clone()); + code + } + + /// Just get a Code object using the same symbol table but without adding it to any library + pub fn snippet(&mut self, code: &str) -> Code { + self.code_builder.code(code) + } + + pub fn in_declarative_region(&mut self, code: &str) -> Code { + self.code( + "libname", + &format!( + " +entity ent is +end entity; + +architecture a of ent is +{code} +begin +end architecture;" + ), + ) + } + + pub fn add_std_logic_1164(&mut self) { + let std_logic_1164 = self.code_builder.code_from_source(std_logic_1164_package()); + self.add_code("ieee", std_logic_1164); + } + + pub fn get_analyzed_root(&self) -> (DesignRoot, Vec) { + let mut root = DesignRoot::new(self.code_builder.symbols.clone()); + let mut diagnostics = Vec::new(); + + add_standard_library(self.symbols(), &mut root); + + for (library_name, codes) in self.libraries.iter() { + for code in codes { + root.add_design_file( + library_name.clone(), + code.design_file_diagnostics(&mut diagnostics), + ); + } + } + root.analyze(&mut diagnostics); + + (root, diagnostics) + } + + pub fn take_code(self) -> Vec<(Symbol, Code)> { + let mut res = Vec::new(); + for (library_name, codes) in self.libraries.into_iter() { + for code in codes.into_iter() { + res.push((library_name.clone(), code)); + } + } + res + } + + pub fn symbols(&self) -> Arc { + self.code_builder.symbols.clone() + } + + pub fn analyze(&self) -> Vec { + self.get_analyzed_root().1 + } +} + +fn standard_package() -> Source { + Source::inline( + Path::new("standard.vhd"), + &Latin1String::new(include_bytes!( + "../../../../vhdl_libraries/std/standard.vhd" + )) + .to_string(), + ) +} +fn textio_package() -> Source { + Source::inline( + Path::new("textio.vhd"), + &Latin1String::new(include_bytes!("../../../../vhdl_libraries/std/textio.vhd")).to_string(), + ) +} + +fn env_package() -> Source { + Source::inline( + Path::new("env.vhd"), + &Latin1String::new(include_bytes!("../../../../vhdl_libraries/std/env.vhd")).to_string(), + ) +} + +fn std_logic_1164_package() -> Source { + Source::inline( + Path::new("std_logic_1164.vhd"), + &Latin1String::new(include_bytes!( + "../../../../vhdl_libraries/ieee2008/std_logic_1164.vhdl" + )) + .to_string(), + ) +} + +pub fn add_standard_library(symbols: Arc, root: &mut DesignRoot) { + let builder = CodeBuilder { + symbols: symbols.clone(), + standard: VHDLStandard::default(), + }; + let std_standard = builder.code_from_source(standard_package()); + let std_textio = builder.code_from_source(textio_package()); + let std_env = builder.code_from_source(env_package()); + let std_sym = symbols.symtab().insert_utf8("std"); + + root.add_design_file(std_sym.clone(), std_standard.design_file()); + root.add_design_file(std_sym.clone(), std_textio.design_file()); + root.add_design_file(std_sym, std_env.design_file()); +} + +pub fn missing(code: &Code, name: &str, occ: usize) -> Diagnostic { + Diagnostic::new( + code.s(name, occ), + format!("No declaration of '{name}'"), + ErrorCode::Unresolved, + ) +} + +pub fn duplicate(code: &Code, name: &str, occ1: usize, occ2: usize) -> Diagnostic { + Diagnostic::new( + code.s(name, occ2), + format!("Duplicate declaration of '{}'", &name), + ErrorCode::Duplicate, + ) + .related(code.s(name, occ1), "Previously defined here") +} + +pub fn duplicates(code: &Code, names: &[&str]) -> Vec { + let mut diagnostics = Vec::new(); + for name in names { + diagnostics.push(duplicate(code, name, 1, 2)); + } + diagnostics +} + +pub fn duplicate_in_two_files(code1: &Code, code2: &Code, names: &[&str]) -> Vec { + let mut diagnostics = Vec::new(); + for name in names { + diagnostics.push( + Diagnostic::new( + code2.s1(name), + format!("Duplicate declaration of '{}'", &name), + ErrorCode::Duplicate, + ) + .related(code1.s1(name), "Previously defined here"), + ) + } + diagnostics +} + +pub fn check_missing(contents: &str) { + let mut builder = LibraryBuilder::new(); + let code = builder.code("libname", contents); + let diagnostics = builder.analyze(); + let occurences = contents.matches("missing").count(); + assert!(occurences > 0); + check_diagnostics( + diagnostics, + (1..=occurences) + .map(|idx| missing(&code, "missing", idx)) + .collect(), + ); +} + +pub fn check_code_with_no_diagnostics(contents: &str) { + let mut builder = LibraryBuilder::new(); + builder.code("libname", contents); + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +pub fn check_search_reference(contents: &str) { + check_search_reference_with_name("decl", contents); +} + +/// Check that all occurrences of decl_name references the first occurrence of if +/// Also check that find all references returns all occurrences of decl_name +pub fn check_search_reference_with_name(decl_name: &str, contents: &str) { + let mut builder = LibraryBuilder::new(); + let code = builder.code("libname", contents); + let occurences = contents.matches(decl_name).count(); + assert!(occurences > 0); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let mut references = Vec::new(); + for idx in 1..=occurences { + assert_eq!( + root.search_reference(code.source(), code.s(decl_name, idx).end()) + .and_then(|ent| ent.declaration().decl_pos().cloned()), + Some(code.s(decl_name, 1).pos()), + "{decl_name}, occurence {}", + idx + ); + references.push(code.s(decl_name, idx).pos()); + } + assert_eq!( + root.find_all_references_pos(&code.s(decl_name, 1).pos()), + references, + ); +} diff --git a/vhdl_lang/src/analysis/tests/view_declarations.rs b/vhdl_lang/src/analysis/tests/view_declarations.rs new file mode 100644 index 0000000..cd88868 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/view_declarations.rs @@ -0,0 +1,619 @@ +use crate::analysis::tests::{check_diagnostics, check_no_diagnostics, LibraryBuilder}; +use crate::data::ErrorCode; +use crate::Diagnostic; +use crate::VHDLStandard::VHDL2019; +use pretty_assertions::assert_eq; + +#[test] +pub fn view_mode_declaration_must_have_declared_subtype() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +view my_view of undeclared is +end view; + ", + ); + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("undeclared"), + "No declaration of 'undeclared'", + ErrorCode::Unresolved, + )], + ) +} + +#[test] +pub fn view_mode_declaration_must_have_record_as_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is (A, B); + +view my_view of foo is +end view; + ", + ); + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s("foo", 2), + "The type of a view must be a record type, not type 'foo'", + ErrorCode::TypeMismatch, + ) + .related(code.s1("foo"), "type 'foo' declared here")], + ); + + let mut builder = LibraryBuilder::with_standard(VHDL2019); + builder.in_declarative_region( + "\ +type foo is record + x: bit; +end record; + +view my_view of foo is + x : in; +end view; + ", + ); + check_no_diagnostics(&builder.analyze()); +} + +#[test] +pub fn element_in_view_that_is_not_in_record() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is record + bar: bit; +end record; + +view my_view of foo is + baz: in; + bar: out; +end view; + ", + ); + let (_, diag) = builder.get_analyzed_root(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("baz"), + "Not a part of record type 'foo'", + ErrorCode::Unresolved, + )], + ); +} + +#[test] +pub fn view_reference_set_to_the_original_element() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is record + bar: bit; +end record; + +view my_view of foo is + bar: in; +end view; + ", + ); + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag); + let record_element = root + .search_reference(code.source(), code.s1("bar: bit").s1("bar").start()) + .unwrap(); + let view_element = root + .search_reference(code.source(), code.s1("bar: in").s1("bar").start()) + .unwrap(); + assert_eq!(record_element, view_element) +} + +#[test] +pub fn diagnostic_when_elements_are_missing() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is record + bar: bit; + baz: bit; +end record; + +view my_view of foo is + bar: in; +end view; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("my_view"), + "Missing association of element 'baz'", + ErrorCode::Unassociated, + )], + ); + + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is record + bar: bit; + baz: bit; + foobar: bit; +end record; + +view my_view of foo is + bar: in; +end view; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("my_view"), + "Missing association of elements 'baz' and 'foobar'", + ErrorCode::Unassociated, + )], + ); + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is record + bar: bit; + baz: bit; + foobar: bit; + foobaz: bit; +end record; + +view my_view of foo is + bar: in; +end view; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("my_view"), + "Missing association of elements 'baz', 'foobar' and 'foobaz'", + ErrorCode::Unassociated, + )], + ); + + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.in_declarative_region( + "\ +type foo is record + a, b, c, d, e, f, g, h: bit; +end record; + +view my_view of foo is + a: in; +end view; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("my_view"), + "Missing association of elements 'b', 'c', 'd' and 4 more", + ErrorCode::Unassociated, + )], + ); +} + +#[test] +pub fn view_interface_declarations_must_be_views() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +entity my_ent is +port ( + foo: view not_declared +); +end entity; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("not_declared"), + "No declaration of 'not_declared'", + ErrorCode::Unresolved, + )], + ); + + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ + entity my_ent is + port ( + foo: view bit + ); + end entity; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::mismatched_kinds( + code.s1("bit"), + "type 'BIT' is not a view", + )], + ); + + let mut builder = LibraryBuilder::with_standard(VHDL2019); + builder.code( + "libname", + "\ +package x is + type bar is record + x: bit; + end bar; + + view foo of bar is + x: in; + end view; +end x; + +use work.x.all; + +entity my_ent is +port ( + foo: view foo +); +end entity; + ", + ); + let diag = builder.analyze(); + check_no_diagnostics(&diag); +} + +#[test] +pub fn view_interface_declaration_subtype_must_match_declared_subtype() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +package x is + type bar is record + x: bit; + end bar; + + view foo of bar is + x: in; + end view; +end x; + +use work.x.all; + +entity my_ent is +port ( + foo: view foo of bit +); +end entity; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("of bit").s1("bit"), + "Specified subtype must match the subtype declared for the view", + ErrorCode::TypeMismatch, + )], + ); + + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +package x is + type bar is record + x: bit; + end bar; + + view foo of bar is + x: in; + end view; +end x; + +use work.x.all; + +entity my_ent is +port ( + x: view foo of bar +); +end entity; + ", + ); + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag); + let in_view = root + .search_reference( + code.source(), + code.s1("x: view foo of bar").s1("bar").start(), + ) + .unwrap(); + + let declared = root + .search_reference( + code.source(), + code.s1("type bar is record").s1("bar").start(), + ) + .unwrap(); + assert_eq!(in_view, declared) +} + +#[test] +fn view_reference() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +package x is + type bar is record + x: bit; + end bar; + + view foo of bar is + x: in; + end view; +end x; + +use work.x; + +entity my_ent is +port ( + x: view x.foo +); +end entity; + ", + ); + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag); + let in_view = root + .search_reference(code.source(), code.s1("x: view x.foo").s1("foo").start()) + .unwrap(); + + let declared = root + .search_reference( + code.source(), + code.s1("view foo of bar is").s1("foo").start(), + ) + .unwrap(); + assert_eq!(in_view, declared) +} + +#[test] +fn converse_attribute() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + builder.code( + "libname", + "\ +package my_pkg is + type bar is record + x: bit; + end bar; + + view foo of bar is + x: in; + end view; + + alias foo_conv is foo'converse; +end my_pkg; + +use work.my_pkg; + +entity my_ent is +port ( + x: view my_pkg.foo; + y: view my_pkg.foo_conv +); +end entity; + ", + ); + let diag = builder.analyze(); + // The mode of views is not analyzed at the moment, so the 'converse attribute + // should simply not show any diagnostics. + check_no_diagnostics(&diag); +} + +#[test] +#[ignore] +fn view_declaration_in_cannot_be_assigned_to() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +package my_pkg is + type bar is record + x: bit; + end bar; + + view foo of bar is + x: in; + end view; +end my_pkg; + +use work.my_pkg; + +entity my_ent is +port ( y: view my_pkg.foo ); +end entity; + +architecture arch of my_ent is +begin + y.x <= '1'; +end arch; + ", + ); + let diag = builder.analyze(); + check_diagnostics( + diag, + vec![Diagnostic::new( + code.s1("y.x"), + "interface signal 'y' of mode in may not be the target of an assignment", + ErrorCode::MismatchedKinds, + )], + ); +} + +// GitHub issue #324 +#[test] +fn view_in_generic_package() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + builder.code( + "libname", + "\ +package test_pkg is + generic ( width : integer ); + type test_t is record + a : bit; + end record; + + view vone of test_t is + a : in; + end view; + alias vtwo is vone'converse; +end package; + +package w8_pkg is new work.test_pkg generic map (width => 8); + +use work.w8_pkg.all; + +entity test_sub_entity is + port ( + my_if : view vone + ); +end entity; + +use work.w8_pkg.all; + +entity test_top_entity is +end entity; + +architecture rtl of test_top_entity is + signal my_sig : test_t; +begin + my_if : entity work.test_sub_entity + port map ( + my_if => my_sig + ); +end architecture; + ", + ); + check_no_diagnostics(&builder.analyze()); +} + +#[test] +fn arrays_of_views_with_matching_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + builder.code( + "libname", + "\ +package test_pkg is + type test_t is record + a : bit; + end record; + + view vone of test_t is + a : in; + end view; + + type test_array is array (natural range <>) of test_t; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_if : view vone; + my_array_if: view (vone) of test_array(0 to 1) + ); +end entity; + ", + ); + check_no_diagnostics(&builder.analyze()); +} + +#[test] +fn arrays_of_views_with_non_matching_type() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +package test_pkg is + type test_t is record + a : bit; + end record; + + view vone of test_t is + a : in; + end view; + + type test_array is array (natural range <>) of bit; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_if : view vone; + my_array_if: view (vone) of test_array(0 to 1) + ); +end entity; + ", + ); + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("test_array(0 to 1)").s1("test_array"), + "Array element type 'BIT' must match record type 'test_t' declared for the view", + ErrorCode::TypeMismatch, + )], + ) +} + +#[test] +fn arrays_of_views_that_are_not_arrays() { + let mut builder = LibraryBuilder::with_standard(VHDL2019); + let code = builder.code( + "libname", + "\ +package test_pkg is + type test_t is record + a : bit; + end record; + + view vone of test_t is + a : in; + end view; +end package; + +use work.test_pkg.all; + +entity test_sub_entity is + port ( + my_if : view vone; + my_array_if: view (vone) of test_t + ); +end entity; + ", + ); + check_diagnostics( + builder.analyze(), + vec![Diagnostic::new( + code.s1("view (vone) of test_t").s1("test_t"), + "Subtype must be an array", + ErrorCode::TypeMismatch, + )], + ) +} diff --git a/vhdl_lang/src/analysis/tests/visibility.rs b/vhdl_lang/src/analysis/tests/visibility.rs new file mode 100644 index 0000000..27da919 --- /dev/null +++ b/vhdl_lang/src/analysis/tests/visibility.rs @@ -0,0 +1,792 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2020, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use vhdl_lang::data::error_codes::ErrorCode; + +#[test] +fn secondary_units_share_root_region_and_visibility_in_extended_region() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg2 is + constant const : natural := 0; +end package; + +package pkg is + use work.pkg2; +end package; + +-- Does not work +use pkg2.const; + +package body pkg is + -- Does work, share visibility of extended region + use pkg2.const; +end package body; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![Diagnostic::new( + code.s("pkg2", 3), + "No declaration of 'pkg2'", + ErrorCode::Unresolved, + )], + ) +} + +#[test] +fn immediate_region_takes_precedence_over_local_visibility() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package false_pkg is + constant decl : natural := 0; +end package; + +package pkg is + constant decl : natural := 1; + use work.false_pkg.decl; + + -- Should refer to constant in pkg + constant ref : natural := decl; +end package pkg; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("decl", 4).start()), + Some(code.s("decl", 2).pos()) + ); +} + +#[test] +fn extended_region_takes_precedence_over_local_visibility() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package false_pkg is + constant decl : natural := 0; +end package; + +package pkg is + constant decl : natural := 1; +end package pkg; + +use work.false_pkg.decl; +package body pkg is + use work.false_pkg.decl; + + -- Should refer to constant in pkg + constant ref : natural := decl; +end package body pkg; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("decl", 5).start()), + Some(code.s("decl", 2).pos()) + ); +} + +#[test] +fn enclosing_region_takes_precedence_over_local_visibility() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + constant decl : natural := 0; +end package; + +entity tb_ent is +end entity; + +architecture a of tb_ent is + constant decl : natural := 1; +begin + main : process + use work.pkg.decl; + begin + assert decl = 1; + end process; +end architecture; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s("decl", 4).start()), + Some(code.s("decl", 2).pos()) + ); +} + +#[test] +fn context_clause_makes_names_visible() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +-- Package will be used for testing +package usepkg is + constant const : natural := 0; + type enum_t is (alpha, beta); +end package; + +context ctx is + library libname; + -- Test both used by name + use libname.usepkg; + -- .. as well as used by all + use libname.usepkg.all; +end context; + + +context work.ctx; +use usepkg.const; + +package pkg is + constant c : enum_t := alpha; +end package; + ", + ); + + let diagnostics = builder.analyze(); + + check_no_diagnostics(&diagnostics); +} + +#[test] +fn cannot_reference_potentially_visible_name_by_selection() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg2 is + constant const1 : natural := 0; + constant const2 : natural := 0; +end package; + + +use work.pkg2.const1; + +package pkg is + use work.pkg2.const2; + constant const3 : natural := 0; +end package; + +use work.pkg.const1; +use work.pkg.const2; +use work.pkg.const3; + +entity ent is +end entity; + ", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::new( + code.s("const1", 3), + "No declaration of 'const1' within package 'pkg'", + ErrorCode::Unresolved, + ), + Diagnostic::new( + code.s("const2", 3), + "No declaration of 'const2' within package 'pkg'", + ErrorCode::Unresolved, + ), + ], + ); +} + +#[test] +fn duplicate_identifer_is_not_directly_visible() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + constant name : natural := 0; +end package; + +package pkg2 is + constant name : natural := 1; +end package; + +use work.pkg1.name; +use work.pkg2.name; + +package user is + constant b : natural := name; +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![hidden_error( + &code, + "name", + 5, + &[ + (&code, "work.pkg1.name", 1, false), + (&code, "name", 1, true), + (&code, "work.pkg2.name", 1, false), + (&code, "name", 2, true), + ], + )], + ); +} + +#[test] +fn duplicate_identifer_visiblity_is_traced_through_context() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "lib", + " +package pkg1 is + constant name : natural := 0; +end package; + +package pkg2 is + constant name : natural := 1; +end package; + +context ctx is + library lib; + use lib.pkg1.name; +end context; + +context work.ctx; +use work.pkg2.name; + +package user is + constant b : natural := name; +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![hidden_error( + &code, + "name", + 5, + &[ + (&code, "work.ctx", 1, false), + (&code, "lib.pkg1.name", 1, false), + (&code, "name", 1, true), + (&code, "work.pkg2.name", 1, false), + (&code, "name", 2, true), + ], + )], + ); +} + +#[test] +fn duplicate_identifer_is_directly_visible_when_it_is_the_same_named_entitty() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg1 is + constant name : natural := 0; +end package; + +use work.pkg1.name; +use work.pkg1.name; + +package user is + constant b : natural := name; +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn duplicate_identifer_of_parent_visibility_is_not_directly_visible() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + constant name : natural := 0; +end package; + +package pkg2 is + constant name : natural := 1; +end package; + +use work.pkg1.name; + +package user is + use work.pkg2.name; + constant b : natural := name; +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![hidden_error( + &code, + "name", + 5, + &[ + (&code, "work.pkg1.name", 1, false), + (&code, "name", 1, true), + (&code, "work.pkg2.name", 1, false), + (&code, "name", 2, true), + ], + )], + ); +} + +#[test] +fn local_is_still_visible_under_duplicate_identifer() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg1 is + constant name : natural := 0; +end package; + +package pkg2 is + constant name : natural := 1; +end package; + + +package user is + use work.pkg1.name; + use work.pkg2.name; + + constant name : natural := 0; + constant b : natural := name; + procedure foo(constant b : natural := name); +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +#[test] +fn mixed_overloaded_names_require_disambiguation() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + function fun1 return natural; + function fun2 return natural; +end package; + +package pkg2 is + constant fun1 : boolean := false; + constant fun2 : natural := 0; +end package; + +use work.pkg.all; +use work.pkg2.all; +package user is + -- Requires disambiguation + constant ident : natural := fun1; + -- Requires disambiguation + constant ident2 : natural := fun2; +end package; +", + ); + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![ + hidden_error( + &code, + "fun1", + 3, + &[ + (&code, "work.pkg.all", 1, false), + (&code, "fun1", 1, true), + (&code, "work.pkg2.all", 1, false), + (&code, "fun1", 2, true), + ], + ), + hidden_error( + &code, + "fun2", + 3, + &[ + (&code, "work.pkg.all", 1, false), + (&code, "fun2", 1, true), + (&code, "work.pkg2.all", 1, false), + (&code, "fun2", 2, true), + ], + ), + ], + ); +} + +/// This was a bug during development +#[test] +fn explicit_library_std_is_not_duplicate_of_implicit() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +library std; +use std.standard.all; + +package pkg1 is +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +/// This was a bug during development +#[test] +fn duplicate_explicit_library_is_not_duplicate() { + let mut builder = LibraryBuilder::new(); + builder.code( + "libname", + " +package pkg is +end package; + +context ctx is + library libname; + use libname.pkg; +end context; + +library libname; +context libname.ctx; +use libname.pkg; + +package user is +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_no_diagnostics(&diagnostics); +} + +pub fn hidden_error( + code: &Code, + name: &str, + occ: usize, + related: &[(&Code, &str, usize, bool)], +) -> Diagnostic { + let mut error = Diagnostic::new( + code.s(name, occ), + format!("Name '{name}' is hidden by conflicting use clause"), + ErrorCode::ConflictingUseClause, + ); + + for (code, substr, occ, declared) in related.iter() { + if *declared { + error.add_related( + code.s(substr, *occ), + format!("Conflicting name '{name}' declared here"), + ) + } else { + error.add_related( + code.s(substr, *occ), + format!("Conflicting name '{name}' made visible here"), + ) + } + } + + error +} + +#[test] +fn duplicate_alias_is_not_directly_visible() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + type enum_t is (alpha, beta); + alias alias_t is enum_t; +end package; + +package pkg2 is + type enum_t is (alpha, beta); + alias alias_t is enum_t; +end package; + +use work.pkg1.alias_t; +use work.pkg2.alias_t; + +package user is + constant b : alias_t := alpha; +end package; + + ", + ); + + let diagnostics = builder.analyze(); + check_diagnostics( + diagnostics, + vec![hidden_error( + &code, + "alias_t", + 5, + &[ + (&code, "work.pkg1.alias_t", 1, false), + (&code, "alias_t", 1, true), + (&code, "work.pkg2.alias_t", 1, false), + (&code, "alias_t", 2, true), + ], + )], + ); +} + +/// It is more convenient for a language server user to goto-declaration for +/// the alias rather than going directly to the declaration without knowing about the visible alias +/// The user can always navigate from the alias to the original declaration if she desires +#[test] +fn deepest_alias_is_visible() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg is + type enum_t is (alpha, beta); +end package; + +package pkg2 is + alias enum_t is work.pkg.enum_t; +end package; + +package pkg3 is + alias enum_t is work.pkg2.enum_t; +end package; + +use work.pkg2.enum_t; +use work.pkg3.enum_t; +use work.pkg.enum_t; + +package pkg4 is + constant c : enum_t := alpha; +end package; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let ref_pos = code.s1("constant c : enum_t := alpha").s1("enum_t"); + let deepest_pos = code.s1("alias enum_t is work.pkg2.enum_t").s1("enum_t"); + + assert_eq!( + root.search_reference_pos(code.source(), ref_pos.start()), + Some(deepest_pos.pos()) + ); +} + +/// Using an overloaded name should not conflict with an immediate declaration if they +/// have different signatures +#[test] +fn non_conflicting_used_names_are_still_visible_in_prescence_of_immediate() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +package pkg1 is + type enum1_t is (alpha, beta); +end package; + +use work.pkg1.enum1_t; + +package user is + type enum2_t is (alpha, beta); + constant a : enum1_t := alpha; + constant b : enum2_t := alpha; +end package; +", + ); + + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + let alpha1_pos = code.s("alpha", 1).pos(); + let alpha1_ref = code.s("alpha", 3).pos(); + assert_eq!( + root.search_reference_pos(code.source(), alpha1_ref.start()), + Some(alpha1_pos.clone()) + ); + + let alpha2_pos = code.s("alpha", 2).pos(); + let alpha2_ref = code.s("alpha", 4).pos(); + assert_eq!( + root.search_reference_pos(code.source(), alpha2_ref.start()), + Some(alpha2_pos.clone()) + ); + + assert_eq_unordered( + &root.find_all_references_pos(&alpha1_pos), + &[alpha1_pos, alpha1_ref], + ); + assert_eq_unordered( + &root.find_all_references_pos(&alpha2_pos), + &[alpha2_pos, alpha2_ref], + ); +} + +#[test] +fn nested_subprogram_shadows_outer() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + " +procedure theproc(arg: character) is +begin +end procedure; + +function thefun(arg: integer) return natural is + procedure theproc(arg: integer) is + begin + theproc('c'); + end procedure; +begin + theproc(arg); + return 0; +end function; +", + ); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("theproc('c')").start()), + Some(code.s1("theproc").pos()) + ); + + assert_eq!( + root.search_reference_pos(code.source(), code.s1("theproc(arg)").start()), + Some(code.s("theproc", 2).pos()) + ); +} + +#[test] +fn labels_are_visible_in_declarative_region() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is + attribute debug : boolean; + attribute debug of main : label is true; +begin + main: process + begin + wait; + end process; +end architecture; + +", + ); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + let label = root + .search_reference(code.source(), code.sa("debug of ", "main").start()) + .unwrap(); + assert_eq!(label.decl_pos(), Some(&code.sb("main", ": process").pos())); +} + +#[test] +fn sequential_labels_are_visible_in_declarative_region() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + " +entity ent is +end entity; + +architecture a of ent is +begin + process + attribute debug : boolean; + attribute debug of main : label is true; + begin + main: for i in 0 to 1 loop + end loop; + wait; + end process; +end architecture; +", + ); + let (root, diagnostics) = builder.get_analyzed_root(); + check_no_diagnostics(&diagnostics); + let label = root + .search_reference(code.source(), code.sa("debug of ", "main").start()) + .unwrap(); + assert_eq!(label.decl_pos(), Some(&code.sb("main", ": for i ").pos())); +} + +#[test] +fn generics_are_visible_in_procedures_but_not_outside() { + let mut builder = LibraryBuilder::new(); + let code = builder.in_declarative_region( + "\ + procedure swap + generic ( type T ) + parameter (a, b : inout T) is + variable temp : T; + begin + temp := a; + a := b; + b := temp; + end procedure swap; + shared variable temp2: T; + ", + ); + let (_, diagnostics) = builder.get_analyzed_root(); + assert_eq!( + diagnostics, + vec![Diagnostic::new( + code.s("T", 4), + "No declaration of 'T'", + ErrorCode::Unresolved + )] + ) +} diff --git a/vhdl_lang/src/analysis/types.rs b/vhdl_lang/src/analysis/types.rs new file mode 100644 index 0000000..35fa1fe --- /dev/null +++ b/vhdl_lang/src/analysis/types.rs @@ -0,0 +1,670 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::ast::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::{Signature, *}; +use crate::HasTokenSpan; +use analyze::*; + +impl<'a, 't> AnalyzeContext<'a, 't> { + pub fn resolve_subtype_indication( + &self, + scope: &Scope<'a>, + subtype_indication: &mut SubtypeIndication, + diagnostics: &mut dyn DiagnosticHandler, + ) -> EvalResult> { + // @TODO more + let SubtypeIndication { + type_mark, + constraint, + .. + } = subtype_indication; + + let base_type = self.type_name(scope, type_mark.span, &mut type_mark.item, diagnostics)?; + + if let Some(constraint) = constraint { + self.analyze_subtype_constraint( + scope, + &type_mark.pos(self.ctx), + base_type.base(), + &mut constraint.item, + diagnostics, + )?; + } + + Ok(Subtype::new(base_type)) + } + + pub(crate) fn analyze_type_declaration( + &self, + scope: &Scope<'a>, + parent: EntRef<'a>, + type_decl: &mut TypeDeclaration, + // Is the full type declaration of an incomplete type + // Overwrite id when defining full type + overwrite_id: Option, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + let src_span = type_decl.span(); + match type_decl.def { + TypeDefinition::Enumeration(ref mut enumeration) => { + let enum_type = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Enum( + enumeration + .iter() + .map(|literal| literal.tree.item.clone().into_designator()) + .collect(), + ), + src_span, + self.source(), + ); + + let signature = + Signature::new(FormalRegion::new(InterfaceType::Parameter), Some(enum_type)); + + for literal in enumeration.iter_mut() { + let literal_ent = self.arena.explicit( + literal.tree.item.clone().into_designator(), + enum_type.into(), + AnyEntKind::Overloaded(Overloaded::EnumLiteral(signature.clone())), + Some(literal.pos(self.ctx)), + src_span, + Some(self.source()), + ); + literal.decl.set(literal_ent.id()); + + unsafe { + self.arena.add_implicit(enum_type.id(), literal_ent); + } + + scope.add(literal_ent, diagnostics); + } + + scope.add(enum_type.into(), diagnostics); + + for ent in self.enum_implicits(enum_type, self.has_matching_op(enum_type)) { + unsafe { + self.arena.add_implicit(enum_type.id(), ent); + } + + scope.add(ent, diagnostics); + } + } + TypeDefinition::ProtectedBody(ref mut body) => { + match scope.lookup_immediate(&type_decl.ident.tree.item.clone().into()) { + Some(visible) => { + let is_ok = match visible.clone().into_non_overloaded() { + Ok(ent) => { + if let AnyEntKind::Type(Type::Protected(ptype_region, is_body)) = + ent.kind() + { + if *is_body { + if let Some(prev_pos) = ent.decl_pos() { + diagnostics.push(Diagnostic::duplicate_error( + &type_decl.ident.tree, + type_decl.ident.tree.pos(self.ctx), + Some(prev_pos), + )) + } + } else { + let ptype_body: EntRef<'_> = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + Some(ent), + Type::Protected(Region::default(), true), + src_span, + self.source(), + ) + .into(); + + let region = Scope::extend(ptype_region, Some(scope)); + self.analyze_declarative_part( + ®ion, + ptype_body, + &mut body.decl, + diagnostics, + )?; + + let kind = Type::Protected(region.into_region(), true); + unsafe { + ptype_body.set_kind(AnyEntKind::Type(kind)); + } + + scope.add(ptype_body, diagnostics); + } + + true + } else { + false + } + } + _ => false, + }; + + if !is_ok { + diagnostics.add( + type_decl.ident.pos(self.ctx), + format!("'{}' is not a protected type", &type_decl.ident), + ErrorCode::TypeMismatch, + ); + } + } + None => { + diagnostics.add( + type_decl.ident.pos(self.ctx), + format!("No declaration of protected type '{}'", &type_decl.ident), + ErrorCode::Unresolved, + ); + } + }; + } + TypeDefinition::Protected(ref mut prot_decl) => { + // Protected type name is visible inside its declarative region + // This will be overwritten later when the protected type region is finished + let ptype: EntRef<'_> = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Protected(Region::default(), false), + src_span, + self.source(), + ) + .into(); + + scope.add(ptype, diagnostics); + + let region = scope.nested(); + for item in prot_decl.items.iter_mut() { + match item { + ProtectedTypeDeclarativeItem::Subprogram(ref mut subprogram) => { + match as_fatal(self.subprogram_specification( + scope, + ptype, + &mut subprogram.specification, + subprogram.span, + Overloaded::SubprogramDecl, + diagnostics, + ))? { + Some((_, ent)) => { + region.add(ent, diagnostics); + } + None => { + return Ok(()); + } + } + } + } + } + + // This is safe since we are in a single thread and no other reference can exist yes + // Also the region is stored inside an Arc which cannot move + { + let AnyEntKind::Type(Type::Protected(region_ptr, _)) = ptype.kind() else { + unreachable!(); + }; + + #[allow(invalid_reference_casting)] + let region_ptr = unsafe { + let region_ptr = region_ptr as *const Region<'_>; + let region_ptr = region_ptr as *mut Region<'_>; + &mut *region_ptr as &mut Region<'_> + }; + *region_ptr = region.into_region(); + } + } + TypeDefinition::Record(ref mut element_decls) => { + let type_ent = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Record(RecordRegion::default()), + src_span, + self.source(), + ); + + let mut elems = RecordRegion::default(); + let mut region = Region::default(); + for elem_decl in element_decls.iter_mut() { + let subtype = + self.resolve_subtype_indication(scope, &mut elem_decl.subtype, diagnostics); + if let Some(subtype) = as_fatal(subtype)? { + for ident in &mut elem_decl.idents { + let elem = self.define( + ident, + type_ent.into(), + AnyEntKind::ElementDeclaration(subtype), + elem_decl.span, + ); + region.add(elem, diagnostics); + elems.add(elem); + } + } + } + region.close(diagnostics); + + unsafe { + let kind = AnyEntKind::Type(Type::Record(elems)); + type_ent.set_kind(kind) + } + + scope.add(type_ent.into(), diagnostics); + + for ent in self.record_implicits(type_ent) { + unsafe { + self.arena.add_implicit(type_ent.id(), ent); + } + scope.add(ent, diagnostics); + } + } + TypeDefinition::Access(ref mut subtype_indication) => { + let subtype = + self.resolve_subtype_indication(scope, subtype_indication, diagnostics); + if let Some(subtype) = as_fatal(subtype)? { + let type_ent = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Access(subtype), + src_span, + self.source(), + ); + + scope.add(type_ent.into(), diagnostics); + + for ent in self.access_implicits(type_ent) { + unsafe { + self.arena.add_implicit(type_ent.id(), ent); + } + scope.add(ent, diagnostics); + } + } + } + TypeDefinition::Array(ref mut array_indexes, _, ref mut subtype_indication) => { + let mut indexes: Vec>> = + Vec::with_capacity(array_indexes.len()); + for index in array_indexes.iter_mut() { + indexes.push(as_fatal(self.analyze_array_index( + scope, + index, + diagnostics, + ))?); + } + + let elem_type = match as_fatal(self.resolve_subtype_indication( + scope, + subtype_indication, + diagnostics, + ))? { + Some(subtype) => subtype.type_mark().to_owned(), + None => return Ok(()), + }; + + let is_1d = indexes.len() == 1; + let array_ent = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Array { indexes, elem_type }, + src_span, + self.source(), + ); + + scope.add(array_ent.into(), diagnostics); + + for ent in self.array_implicits(array_ent, is_1d && self.has_matching_op(elem_type)) + { + unsafe { + self.arena.add_implicit(array_ent.id(), ent); + } + scope.add(ent, diagnostics); + } + } + TypeDefinition::Subtype(ref mut subtype_indication) => { + if let Some(subtype) = as_fatal(self.resolve_subtype_indication( + scope, + subtype_indication, + diagnostics, + ))? { + let type_ent = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Subtype(subtype), + src_span, + self.source(), + ); + scope.add(type_ent.into(), diagnostics); + } + } + TypeDefinition::Physical(ref mut physical) => { + self.range_with_ttyp( + scope, + self.universal_integer().into(), + &mut physical.range, + diagnostics, + )?; + + let phys_type = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::Physical, + src_span, + self.source(), + ); + scope.add(phys_type.into(), diagnostics); + + let primary = self.define( + &mut physical.primary_unit, + parent, + AnyEntKind::PhysicalLiteral(phys_type), + src_span, + ); + + unsafe { + self.arena.add_implicit(phys_type.id(), primary); + } + scope.add(primary, diagnostics); + + for (secondary_unit_name, value) in physical.secondary_units.iter_mut() { + match self.resolve_physical_unit(scope, &mut value.item.unit) { + Ok(secondary_unit_type) => { + if secondary_unit_type.base_type() != phys_type { + diagnostics.add( + value.item.unit.item.pos(self.ctx), + format!( + "Physical unit of type '{}' does not match {}", + secondary_unit_type.designator(), + phys_type.describe() + ), + ErrorCode::TypeMismatch, + ) + } + } + Err(err) => diagnostics.push(err), + } + + let secondary_unit = self.define( + secondary_unit_name, + parent, + AnyEntKind::PhysicalLiteral(phys_type), + src_span, + ); + unsafe { + self.arena.add_implicit(phys_type.id(), secondary_unit); + } + scope.add(secondary_unit, diagnostics) + } + + for ent in self.physical_implicits(phys_type) { + unsafe { + self.arena.add_implicit(phys_type.id(), ent); + } + scope.add(ent, diagnostics); + } + } + TypeDefinition::Incomplete(..) => { + unreachable!("Handled elsewhere"); + } + + TypeDefinition::Numeric(ref mut range) => { + self.range_unknown_typ(scope, range, diagnostics)?; + + let universal_type = if let Some(range_typ) = + as_fatal(self.range_type(scope, range, diagnostics))? + { + if range_typ.is_any_integer() { + UniversalType::Integer + } else if range_typ.is_any_real() { + UniversalType::Real + } else { + diagnostics.add( + range.span().pos(self.ctx), + "Expected real or integer range", + ErrorCode::TypeMismatch, + ); + return Ok(()); + } + } else { + return Ok(()); + }; + + let type_ent = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + match universal_type { + UniversalType::Integer => Type::Integer, + UniversalType::Real => Type::Real, + }, + src_span, + self.source(), + ); + scope.add(type_ent.into(), diagnostics); + + for ent in self.numeric_implicits(universal_type, type_ent) { + unsafe { + self.arena.add_implicit(type_ent.id(), ent); + } + scope.add(ent, diagnostics); + } + } + + TypeDefinition::File(ref mut type_mark) => { + let file_type = TypeEnt::define_with_opt_id( + self.ctx, + self.arena, + overwrite_id, + &mut type_decl.ident, + parent, + None, + Type::File, + src_span, + self.source(), + ); + + if let Some(type_mark) = as_fatal(self.type_name( + scope, + type_mark.span, + &mut type_mark.item, + diagnostics, + ))? { + for ent in self.create_implicit_file_type_subprograms(file_type, type_mark) { + unsafe { + self.arena.add_implicit(file_type.id(), ent); + } + scope.add(ent, diagnostics); + } + } + + scope.add(file_type.into(), diagnostics); + } + } + + Ok(()) + } + + /// The matching operators such as ?= are defined for 1d arrays of bit and std_ulogic element type + fn has_matching_op(&self, typ: TypeEnt<'a>) -> bool { + if self.is_std_logic_1164 { + // Within the std_logic_1164 we do not have efficient access to the types + typ.designator() == &Designator::Identifier(self.root.symbol_utf8("std_ulogic")) + } else { + if let Some(ref standard_types) = self.root.standard_types { + if typ.id() == standard_types.bit { + return true; + } + } + + if let Some(id) = self.root.std_ulogic { + if typ.id() == id { + return true; + } + } + + false + } + } + + fn analyze_subtype_constraint( + &self, + scope: &Scope<'a>, + pos: &SrcPos, // The position of the root type mark + base_type: BaseType<'a>, + constraint: &mut SubtypeConstraint, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + match constraint { + SubtypeConstraint::Array(ref mut dranges, ref mut constraint) => { + if let Type::Array { indexes, elem_type } = base_type.kind() { + for (idx, drange) in dranges.iter_mut().enumerate() { + if let Some(index_typ) = indexes.get(idx) { + if let Some(index_typ) = index_typ { + self.drange_with_ttyp( + scope, + (*index_typ).into(), + &mut drange.item, + diagnostics, + )?; + } else { + self.drange_unknown_type(scope, &mut drange.item, diagnostics)?; + } + } else { + diagnostics.add( + drange.span().pos(self.ctx), + format!("Got extra index constraint for {}", base_type.describe()), + ErrorCode::TooManyConstraints, + ); + } + } + + // empty dranges means (open) + if dranges.len() < indexes.len() && !dranges.is_empty() { + diagnostics.add( + pos, + format!( + "Too few index constraints for {}. Got {} but expected {}", + base_type.describe(), + dranges.len(), + indexes.len() + ), + ErrorCode::TooFewConstraints, + ); + } + + if let Some(constraint) = constraint { + self.analyze_subtype_constraint( + scope, + &constraint.span.pos(self.ctx), + elem_type.base(), + &mut constraint.item, + diagnostics, + )?; + } + } else { + diagnostics.add( + pos, + format!( + "Array constraint cannot be used for {}", + base_type.describe() + ), + ErrorCode::IllegalConstraint, + ); + } + } + SubtypeConstraint::Range(ref mut range) => { + if base_type.is_scalar() { + self.range_with_ttyp(scope, base_type.into(), range, diagnostics)?; + } else { + diagnostics.add( + pos, + format!( + "Scalar constraint cannot be used for {}", + base_type.describe() + ), + ErrorCode::IllegalConstraint, + ); + } + } + SubtypeConstraint::Record(ref mut constraints) => { + if let Type::Record(region) = base_type.kind() { + for constraint in constraints.iter_mut() { + let ElementConstraint { ident, constraint } = constraint; + let des = Designator::Identifier(ident.item.clone()); + if let Some(elem) = region.lookup(&des) { + self.analyze_subtype_constraint( + scope, + &constraint.pos(self.ctx), + elem.type_mark().base(), + &mut constraint.item, + diagnostics, + )?; + } else { + diagnostics.push(Diagnostic::no_declaration_within( + &base_type, + ident.pos(self.ctx), + &des, + )) + } + } + } else { + diagnostics.add( + pos, + format!( + "Record constraint cannot be used for {}", + base_type.describe() + ), + ErrorCode::IllegalConstraint, + ); + } + } + } + Ok(()) + } + + pub fn analyze_subtype_indication( + &self, + scope: &Scope<'a>, + subtype_indication: &mut SubtypeIndication, + diagnostics: &mut dyn DiagnosticHandler, + ) -> FatalResult { + as_fatal(self.resolve_subtype_indication(scope, subtype_indication, diagnostics)) + .map(|_| ()) + } +} diff --git a/vhdl_lang/src/ast.rs b/vhdl_lang/src/ast.rs new file mode 100644 index 0000000..376390a --- /dev/null +++ b/vhdl_lang/src/ast.rs @@ -0,0 +1,1649 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +mod display; +mod util; + +#[macro_use] +mod any_design_unit; + +#[macro_use] +pub mod search; +mod ast_span; +pub mod token_range; + +pub(crate) use self::util::*; +use crate::ast::token_range::*; +use crate::data::*; +use crate::named_entity::{EntityId, Reference}; +use crate::syntax::{Token, TokenAccess, TokenId}; +use crate::TokenSpan; +pub(crate) use any_design_unit::*; +use vhdl_lang::HasTokenSpan; + +/// LRM 15.8 Bit string literals +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum BaseSpecifier { + B, + O, + X, + UB, + UO, + UX, + SB, + SO, + SX, + D, +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub enum Operator { + And, + Or, + Nand, + Nor, + Xor, + Xnor, + Abs, + Not, + Minus, + Plus, + QueQue, // ?? conditional operator + + EQ, + NE, + LT, + LTE, + GT, + GTE, + QueEQ, + QueNE, + QueLT, + QueLTE, + QueGT, + QueGTE, + + SLL, + SRL, + SLA, + SRA, + ROL, + ROR, + + Concat, + + Times, + Div, + Mod, + Rem, + + Pow, +} + +/// LRM 8.6 Attribute names +#[derive(PartialEq, Debug, Clone)] +pub struct AttributeName { + pub name: WithTokenSpan, + pub signature: Option>, + pub attr: WithToken, + pub expr: Option>>, +} + +#[derive(PartialEq, Debug, Copy, Clone, Eq)] +pub enum TypeAttribute { + Subtype, + Element, +} + +#[derive(PartialEq, Debug, Copy, Clone, Eq)] +pub enum RangeAttribute { + Range, + ReverseRange, +} + +#[derive(PartialEq, Debug, Clone, Eq)] +pub enum AttributeDesignator { + Type(TypeAttribute), + Range(RangeAttribute), + Ident(WithRef), + Ascending, + Left, + Right, + High, + Low, + Length, + Image, + Value, + Pos, + Val, + Succ, + Pred, + LeftOf, + RightOf, + Signal(SignalAttribute), + SimpleName, + InstanceName, + PathName, + Converse, +} + +#[derive(PartialEq, Debug, Copy, Clone, Eq)] +pub enum SignalAttribute { + Delayed, + Stable, + Quiet, + Transaction, + Event, + Active, + LastEvent, + LastActive, + LastValue, + Driving, + DrivingValue, +} + +/// LRM 8.7 External names +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum ExternalObjectClass { + Constant, + Signal, + Variable, +} + +impl From for ObjectClass { + fn from(object: ExternalObjectClass) -> ObjectClass { + match object { + ExternalObjectClass::Constant => ObjectClass::Constant, + ExternalObjectClass::Variable => ObjectClass::Variable, + ExternalObjectClass::Signal => ObjectClass::Signal, + } + } +} + +/// LRM 8.7 External names +#[derive(PartialEq, Debug, Clone)] +pub enum ExternalPath { + Package(WithTokenSpan), + Absolute(WithTokenSpan), + + // usize field indicates the number of up-levels ('^') + Relative(WithTokenSpan, usize), +} + +/// LRM 8.7 External names +#[derive(PartialEq, Debug, Clone)] +pub struct ExternalName { + pub class: ExternalObjectClass, + pub path: WithTokenSpan, + pub colon_token: TokenId, + pub subtype: SubtypeIndication, +} + +/// LRM 8. Names +#[derive(PartialEq, Debug, Clone)] +pub enum Name { + Designator(WithRef), + Selected(Box>, WithToken>), + SelectedAll(Box>), + Slice(Box>, Box), + Attribute(Box), + CallOrIndexed(Box), + External(Box), +} + +/// LRM 9.3.4 Function calls +#[derive(PartialEq, Debug, Clone)] +pub struct CallOrIndexed { + pub name: WithTokenSpan, + pub parameters: SeparatedList, +} + +/// LRM 9.3.3 Aggregates +#[derive(PartialEq, Debug, Clone)] +pub enum Choice { + Expression(Expression), + DiscreteRange(DiscreteRange), + Others, +} + +/// LRM 9.3.3 Aggregates +#[derive(PartialEq, Debug, Clone)] +pub enum ElementAssociation { + Positional(WithTokenSpan), + Named(Vec>, WithTokenSpan), +} + +/// LRM 6.5.7 Association Lists +#[derive(PartialEq, Debug, Clone)] +pub enum ActualPart { + Expression(Expression), + Open, +} + +/// LRM 6.5.7 Association Lists +#[derive(PartialEq, Debug, Clone)] +pub struct AssociationElement { + pub formal: Option>, + pub actual: WithTokenSpan, +} + +/// LRM 15.5 Abstract literals +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum AbstractLiteral { + Integer(u64), + Real(f64), +} + +/// LRM 15.8 Bit string literals +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct BitString { + pub length: Option, + pub base: BaseSpecifier, + pub value: Latin1String, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct PhysicalLiteral { + pub value: AbstractLiteral, + pub unit: WithRef, +} + +/// LRM 9.3.2 Literals +#[derive(PartialEq, Debug, Clone)] +pub enum Literal { + String(Latin1String), + BitString(BitString), + Character(u8), + AbstractLiteral(AbstractLiteral), + Physical(PhysicalLiteral), + Null, +} + +/// LRM 9.3.7 Allocators +#[derive(PartialEq, Debug, Clone)] +pub enum Allocator { + Qualified(QualifiedExpression), + Subtype(SubtypeIndication), +} + +/// LRM 9.3.5 Qualified expressions +#[derive(PartialEq, Debug, Clone)] +pub struct QualifiedExpression { + pub type_mark: WithTokenSpan, + pub expr: WithTokenSpan, +} + +/// LRM 9. Expressions +#[derive(PartialEq, Debug, Clone)] +pub enum Expression { + Binary( + WithToken>, + Box>, + Box>, + ), + Unary(WithToken>, Box>), + + /// LRM 9.3.3 Aggregates + Aggregate(Vec>), + + /// LRM 9.3.5 Qualified expressions + Qualified(Box), + + /// LRM 8 Names + Name(Box), + + /// LRM 9.3.2 Literals + Literal(Literal), + + /// LRM 9.3.7 Allocators + New(Box>), + Parenthesized(Box>), +} + +/// An identifier together with the lexical source location it occurs in. +pub type Ident = WithToken; + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum Direction { + Ascending, + Descending, +} + +/// LRM discrete_range +/// discrete_range ::= discrete_subtype_indication | range +/// range ::= +/// range_attribute_name +/// | simple_expression direction simple_expression +#[derive(PartialEq, Debug, Clone)] +pub enum DiscreteRange { + Discrete(WithTokenSpan, Option), + Range(Range), +} + +#[derive(PartialEq, Debug, Clone)] +pub struct RangeConstraint { + pub direction: Direction, + pub left_expr: Box>, + pub right_expr: Box>, +} + +impl RangeConstraint { + pub fn direction_token(&self) -> TokenId { + self.left_expr.span.end_token + 1 + } +} + +#[derive(PartialEq, Debug, Clone)] +pub enum Range { + Range(RangeConstraint), + Attribute(Box), +} + +/// LRM: record_element_constraint +#[derive(PartialEq, Debug, Clone)] +pub struct ElementConstraint { + pub ident: Ident, + pub constraint: Box>, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum SubtypeConstraint { + Range(Range), + /// Empty Vec means Open + Array( + Vec>, + Option>>, + ), + Record(Vec), +} + +/// LRM 6.3 Subtype declarations +#[derive(PartialEq, Debug, Clone)] +pub struct RecordElementResolution { + pub ident: Ident, + pub resolution: Box, +} + +/// LRM 6.3 Subtype declarations +#[derive(PartialEq, Debug, Clone)] +pub enum ResolutionIndication { + FunctionName(WithTokenSpan), + ArrayElement(WithTokenSpan), + Record(WithTokenSpan>), +} + +impl HasTokenSpan for ResolutionIndication { + fn get_start_token(&self) -> TokenId { + match self { + ResolutionIndication::FunctionName(name) => name.get_start_token(), + ResolutionIndication::ArrayElement(name) => name.get_start_token() - 1, + ResolutionIndication::Record(record) => record.get_start_token(), + } + } + + fn get_end_token(&self) -> TokenId { + match self { + ResolutionIndication::FunctionName(name) => name.get_end_token(), + ResolutionIndication::ArrayElement(name) => name.get_end_token() + 1, + ResolutionIndication::Record(record) => record.get_end_token(), + } + } +} + +/// LRM 6.3 Subtype declarations +#[derive(PartialEq, Debug, Clone)] +pub struct SubtypeIndication { + pub resolution: Option, + pub type_mark: WithTokenSpan, + pub constraint: Option>, +} + +/// LRM 5.3 Array Types +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum ArrayIndex { + /// Unbounded + /// {identifier} range <> + IndexSubtypeDefintion(WithTokenSpan), + + /// Constraint + Discrete(WithTokenSpan), +} + +/// LRM 5.3.3 Record types +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ElementDeclaration { + pub idents: Vec>, + pub colon_token: TokenId, + pub subtype: SubtypeIndication, +} + +/// LRM 5.6.2 Protected type declarations +#[derive(PartialEq, Debug, Clone)] +pub enum ProtectedTypeDeclarativeItem { + Subprogram(SubprogramDeclaration), +} + +#[derive(PartialEq, Eq, Hash, Debug, Clone)] +pub enum Designator { + Identifier(Symbol), + OperatorSymbol(Operator), + Character(u8), + Anonymous(usize), +} + +/// An item which has a reference to a declaration +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct WithRef { + pub item: T, + pub reference: Reference, +} + +impl WithRef { + pub fn new(item: T) -> WithRef { + WithRef { + item, + reference: Reference::undefined(), + } + } +} + +/// An item which declares a named entity +#[derive(PartialEq, Debug, Clone)] +pub struct WithDecl { + pub tree: T, + pub decl: Reference, +} + +impl WithDecl { + pub fn new(tree: T) -> WithDecl { + WithDecl { + tree, + decl: Reference::undefined(), + } + } +} + +impl WithDecl> { + pub fn pos<'a>(&'a self, ctx: &'a dyn TokenAccess) -> &SrcPos { + self.tree.pos(ctx) + } +} + +impl From for WithDecl { + fn from(value: T) -> Self { + WithDecl::new(value) + } +} + +impl> AsRef for WithDecl { + fn as_ref(&self) -> &SrcPos { + self.tree.as_ref() + } +} + +impl HasDesignator for WithToken> { + fn designator(&self) -> &Designator { + self.item.designator() + } +} + +/// LRM 6.6 Alias declarations +#[derive(PartialEq, Debug, Clone)] +pub struct AliasDeclaration { + pub designator: WithDecl>, + pub subtype_indication: Option, + pub is_token: TokenId, + pub name: WithTokenSpan, + pub signature: Option>, +} + +/// LRM 6.7 Attribute declarations +#[derive(PartialEq, Debug, Clone)] +pub struct AttributeDeclaration { + pub ident: WithDecl, + pub type_mark: WithTokenSpan, +} + +/// LRM 7.2 Attribute specification +#[derive(PartialEq, Debug, Clone)] +pub struct EntityTag { + pub designator: WithToken>, + pub signature: Option>, +} + +/// LRM 7.2 Attribute specification +#[derive(PartialEq, Debug, Clone)] +pub enum EntityName { + Name(EntityTag), + All, + Others, +} + +/// LRM 7.2 Attribute specification +// @TODO there are more classes +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum EntityClass { + Entity, + Architecture, + Configuration, + Procedure, + Function, + Package, + Type, + Subtype, + Constant, + Signal, + Variable, + Component, + Label, + Literal, + Units, + // Group + File, + // Property + // Sequence +} + +/// LRM 7.2 Attribute specification +#[derive(PartialEq, Debug, Clone)] +pub struct AttributeSpecification { + pub ident: WithRef, + pub entity_name: EntityName, + pub colon_token: TokenId, + pub entity_class: EntityClass, + pub expr: WithTokenSpan, +} + +/// LRM 7.2 Attribute specification +#[derive(PartialEq, Debug, Clone)] +pub enum Attribute { + Specification(AttributeSpecification), + Declaration(AttributeDeclaration), +} + +/// LRM 5.6.2 Protected type declarations +#[derive(PartialEq, Debug, Clone)] +pub struct ProtectedTypeDeclaration { + pub items: Vec, +} + +/// LRM 5.6.3 Protected type bodies +#[derive(PartialEq, Debug, Clone)] +pub struct ProtectedTypeBody { + pub decl: Vec>, +} + +/// LRM 5.4.2 Physical type declaration +#[derive(PartialEq, Debug, Clone)] +pub struct PhysicalTypeDeclaration { + pub range: Range, + pub units_token: TokenId, + pub primary_unit: WithDecl, + pub secondary_units: Vec<(WithDecl, WithTokenSpan)>, +} + +/// LRM 5.2.2 Enumeration types +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum EnumerationLiteral { + Identifier(Symbol), + Character(u8), +} + +/// LRM 5 Types +#[derive(PartialEq, Debug, Clone)] +pub enum TypeDefinition { + /// LRM 5.2 Scalar Types + /// LRM 5.2.2 Enumeration types + Enumeration(Vec>>), + /// LRM 5.2.3 Integer types + /// 5.2.5 Floating-point types + Numeric(Range), + /// LRM 5.2.4 Physical types + Physical(PhysicalTypeDeclaration), + // @TODO floating + /// LRM 5.3 Composite Types + /// LRM 5.3.2 Array types + Array(Vec, TokenId, SubtypeIndication), + /// LRM 5.3.3 Record types + Record(Vec), + /// LRM 5.4 Access types + Access(SubtypeIndication), + /// LRM 5.4.2 Incomplete type declarations + Incomplete(Reference), + /// LRM 5.5 File types + File(WithTokenSpan), + /// LRM 5.6 Protected types + Protected(ProtectedTypeDeclaration), + ProtectedBody(ProtectedTypeBody), + /// LRM 6.3 Subtype declarations + Subtype(SubtypeIndication), +} + +/// LRM 6.2 Type declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct TypeDeclaration { + pub ident: WithDecl, + pub def: TypeDefinition, + pub end_ident_pos: Option, +} + +impl TypeDeclaration { + pub fn is_token(&self) -> Option { + if matches!(self.def, TypeDefinition::Incomplete(_)) { + // incomplete types have no `is` token + None + } else { + Some(self.ident.tree.token + 1) + } + } +} + +/// LRM 6.4.2 Object Declarations +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum ObjectClass { + Signal, + Constant, + Variable, + SharedVariable, +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum InterfaceType { + Port, + Generic, + Parameter, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct ObjectDeclaration { + pub class: ObjectClass, + pub colon_token: TokenId, + pub idents: Vec>, + pub subtype_indication: SubtypeIndication, + pub expression: Option>, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct FileDeclaration { + pub idents: Vec>, + pub colon_token: TokenId, + pub subtype_indication: SubtypeIndication, + pub open_info: Option<(TokenId, WithTokenSpan)>, + pub file_name: Option<(TokenId, WithTokenSpan)>, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum SubprogramDesignator { + Identifier(Symbol), + OperatorSymbol(Operator), +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct InterfaceList { + pub interface_type: InterfaceType, + pub items: Vec, +} + +/// LRM 4.2 Subprogram declaration +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ProcedureSpecification { + pub designator: WithDecl>, + pub header: Option, + pub parameter_list: Option, +} + +/// LRM 4.2 Subprogram declaration +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct FunctionSpecification { + pub pure: bool, + pub designator: WithDecl>, + pub header: Option, + pub parameter_list: Option, + pub return_type: WithTokenSpan, +} + +/// LRM 4.3 Subprogram bodies +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct SubprogramBody { + pub specification: SubprogramSpecification, + pub declarations: Vec>, + pub begin_token: TokenId, + pub statements: Vec, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +/// LRM 4.2.1 Subprogram Header +/// Note that, as opposed to the standard, the header is not optional. +/// Instead, the element that contains the header (e.g., procedure specifications) +/// mark this element as optional. +#[derive(PartialEq, Debug, Clone)] +pub struct SubprogramHeader { + pub generic_list: InterfaceList, + pub map_aspect: Option, +} + +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum SubprogramKind { + Function, + Procedure, +} + +/// LRM 4.4 Subprogram Instantiation Statement +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct SubprogramInstantiation { + pub kind: SubprogramKind, + pub ident: WithDecl, + pub subprogram_name: WithTokenSpan, + pub signature: Option>, + pub generic_map: Option, +} + +/// LRM 4.5.3 Signatures +#[derive(PartialEq, Debug, Clone)] +pub enum Signature { + Function(Vec>, WithTokenSpan), + Procedure(Vec>), +} + +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum SubprogramSpecification { + Procedure(ProcedureSpecification), + Function(FunctionSpecification), +} + +/// LRM 4.2 Subprogram declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct SubprogramDeclaration { + pub specification: SubprogramSpecification, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct InterfaceFileDeclaration { + pub idents: Vec>, + pub colon_token: TokenId, + pub subtype_indication: SubtypeIndication, +} + +/// LRM 6.5.2 Interface object declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct InterfaceObjectDeclaration { + pub list_type: InterfaceType, + pub colon_token: TokenId, + pub idents: Vec>, + pub mode: ModeIndication, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum ModeIndication { + Simple(SimpleModeIndication), + View(ModeViewIndication), +} + +#[derive(PartialEq, Debug, Clone)] +pub struct SimpleModeIndication { + pub mode: Option>, + pub class: ObjectClass, + pub subtype_indication: SubtypeIndication, + pub bus: bool, + pub expression: Option>, +} + +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum ModeViewIndicationKind { + Array, + Record, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ModeViewIndication { + pub kind: ModeViewIndicationKind, + pub name: WithTokenSpan, + pub subtype_indication: Option<(TokenId, SubtypeIndication)>, +} + +/// LRM 6.5.5 Interface package declaration +#[derive(PartialEq, Debug, Clone)] +pub enum InterfacePackageGenericMapAspect { + Map(SeparatedList), + Box, + Default, +} + +/// LRM 6.5.5 Interface package declaration +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct InterfacePackageDeclaration { + pub ident: WithDecl, + pub package_name: WithTokenSpan, + pub generic_map: WithTokenSpan, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum SubprogramDefault { + Name(WithTokenSpan), + Box, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct InterfaceSubprogramDeclaration { + pub specification: SubprogramSpecification, + pub default: Option, +} + +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum InterfaceDeclaration { + Object(InterfaceObjectDeclaration), + File(InterfaceFileDeclaration), + Type(WithDecl), + /// LRM 6.5.4 Interface subprogram declarations + Subprogram(InterfaceSubprogramDeclaration), + /// LRM 6.5.5 Interface package declaration + Package(InterfacePackageDeclaration), +} + +#[derive(PartialEq, Eq, Debug, Clone, Copy, Default)] +pub enum Mode { + #[default] + In, + Out, + InOut, + Buffer, + Linkage, +} + +/// LRM 6.8 Component declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ComponentDeclaration { + pub ident: WithDecl, + pub is_token: Option, + pub generic_list: Option, + pub port_list: Option, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum Declaration { + Object(ObjectDeclaration), + File(FileDeclaration), + Type(TypeDeclaration), + Component(ComponentDeclaration), + Attribute(Attribute), + Alias(AliasDeclaration), + SubprogramDeclaration(SubprogramDeclaration), + SubprogramInstantiation(SubprogramInstantiation), + SubprogramBody(SubprogramBody), + Use(UseClause), + Package(PackageInstantiation), + Configuration(ConfigurationSpecification), + View(ModeViewDeclaration), +} + +impl Declaration { + pub fn declarations(&self) -> Vec { + match self { + Declaration::Object(ObjectDeclaration { idents, .. }) + | Declaration::File(FileDeclaration { idents, .. }) => { + idents.iter().flat_map(|ident| ident.decl.get()).collect() + } + Declaration::Type(TypeDeclaration { ident, .. }) + | Declaration::Component(ComponentDeclaration { ident, .. }) + | Declaration::View(ModeViewDeclaration { ident, .. }) + | Declaration::Package(PackageInstantiation { ident, .. }) + | Declaration::SubprogramInstantiation(SubprogramInstantiation { ident, .. }) + | Declaration::Attribute(Attribute::Declaration(AttributeDeclaration { + ident, .. + })) => ident.decl.get().into_iter().collect(), + Declaration::Alias(alias) => alias.designator.decl.get().into_iter().collect(), + Declaration::SubprogramDeclaration(SubprogramDeclaration { specification, .. }) + | Declaration::SubprogramBody(SubprogramBody { specification, .. }) => { + let designator = match specification { + SubprogramSpecification::Procedure(procedure) => &procedure.designator, + SubprogramSpecification::Function(function) => &function.designator, + }; + designator.decl.get().into_iter().collect() + } + _ => vec![], + } + } +} + +/// LRM 10.2 Wait statement +#[derive(PartialEq, Debug, Clone)] +pub struct WaitStatement { + pub sensitivity_clause: Option>>, + pub condition_clause: Option>, + pub timeout_clause: Option>, +} + +/// LRM 10.3 Assertion statement +#[derive(PartialEq, Debug, Clone)] +pub struct AssertStatement { + pub condition: WithTokenSpan, + pub report: Option>, + pub severity: Option>, +} + +/// LRM 10.4 Report statement +#[derive(PartialEq, Debug, Clone)] +pub struct ReportStatement { + pub report: WithTokenSpan, + pub severity: Option>, +} + +/// LRM 10.5 Signal assignment statement +#[derive(PartialEq, Debug, Clone)] +pub enum Target { + Name(Name), + Aggregate(Vec>), +} + +/// LRM 10.5 Signal assignment statement +#[derive(PartialEq, Debug, Clone)] +pub struct WaveformElement { + pub value: WithTokenSpan, + pub after: Option>, +} + +impl HasTokenSpan for WaveformElement { + fn get_start_token(&self) -> TokenId { + self.value.get_start_token() + } + + fn get_end_token(&self) -> TokenId { + self.after + .as_ref() + .map(|expr| expr.get_end_token()) + .unwrap_or(self.value.get_end_token()) + } +} + +/// LRM 10.5 Signal assignment statement +#[derive(PartialEq, Debug, Clone)] +pub enum Waveform { + Elements(Vec), + Unaffected(TokenId), +} + +/// LRM 10.5 Signal assignment statement +#[derive(PartialEq, Debug, Clone)] +pub enum DelayMechanism { + Transport, + Inertial { + reject: Option>, + }, +} + +/// LRM 10.5 Signal assignment statement +#[derive(PartialEq, Debug, Clone)] +pub struct SignalAssignment { + pub target: WithTokenSpan, + pub delay_mechanism: Option>, + pub rhs: AssignmentRightHand, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum ForceMode { + In, + Out, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct SignalForceAssignment { + pub target: WithTokenSpan, + pub force_mode: Option, + pub rhs: AssignmentRightHand>, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct SignalReleaseAssignment { + pub target: WithTokenSpan, + pub force_mode: Option, +} + +/// LRM 10.6 Variable assignment statement +#[derive(PartialEq, Debug, Clone)] +pub struct VariableAssignment { + pub target: WithTokenSpan, + pub rhs: AssignmentRightHand>, +} + +/// LRM 10.5 Signal assignment statement +/// LRM 10.6 Variable assignment statement +#[derive(PartialEq, Debug, Clone)] +pub enum AssignmentRightHand { + Simple(T), + Conditional(Conditionals), + Selected(Selection), +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Conditional { + pub condition: WithTokenSpan, + pub item: T, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Conditionals { + pub conditionals: Vec>, + pub else_item: Option<(T, TokenId)>, +} + +/// LRM 10.8 If statement +#[derive(PartialEq, Debug, Clone)] +pub struct IfStatement { + pub conds: Conditionals>, + pub end_label_pos: Option, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct Alternative { + pub choices: Vec>, + pub item: T, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Selection { + pub expression: WithTokenSpan, + pub alternatives: Vec>, +} + +/// LRM 10.9 Case statement +#[derive(PartialEq, Debug, Clone)] +pub struct CaseStatement { + pub is_matching: bool, + pub expression: WithTokenSpan, + pub alternatives: Vec>>, + pub end_token: TokenId, + pub end_label_pos: Option, +} + +/// LRM 10.10 Loop statement +#[derive(PartialEq, Debug, Clone)] +pub enum IterationScheme { + While(WithTokenSpan), + For(WithDecl, DiscreteRange), +} + +/// LRM 10.10 Loop statement +#[derive(PartialEq, Debug, Clone)] +pub struct LoopStatement { + pub iteration_scheme: Option, + pub loop_token: TokenId, + pub statements: Vec, + pub end_token: TokenId, + pub end_label_pos: Option, +} + +/// LRM 10.11 Next statement +#[derive(PartialEq, Debug, Clone)] +pub struct NextStatement { + pub loop_label: Option>, + pub condition: Option>, +} + +/// LRM 10.12 Exit statement +#[derive(PartialEq, Debug, Clone)] +pub struct ExitStatement { + pub loop_label: Option>, + pub condition: Option>, +} + +/// LRM 10.13 Return statement +#[derive(PartialEq, Debug, Clone)] +pub struct ReturnStatement { + pub expression: Option>, +} + +/// LRM 10. Sequential statements +#[derive(PartialEq, Debug, Clone)] +pub enum SequentialStatement { + Wait(WaitStatement), + Assert(AssertStatement), + Report(ReportStatement), + VariableAssignment(VariableAssignment), + SignalAssignment(SignalAssignment), + SignalForceAssignment(SignalForceAssignment), + SignalReleaseAssignment(SignalReleaseAssignment), + ProcedureCall(WithTokenSpan), + If(IfStatement), + Case(CaseStatement), + Loop(LoopStatement), + Next(NextStatement), + Exit(ExitStatement), + Return(ReturnStatement), + Null, +} + +/// LRM 10. Sequential statements +#[derive(PartialEq, Debug, Clone)] +pub struct LabeledSequentialStatement { + pub label: WithDecl>, + pub statement: WithTokenSpan, +} + +/// LRM 11.2 Block statement +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct BlockStatement { + pub guard_condition: Option>, + pub header: BlockHeader, + pub is_token: Option, + pub decl: Vec>, + pub begin_token: TokenId, + pub statements: Vec, + pub end_token: TokenId, + pub end_label_pos: Option, +} + +/// LRM 11.2 Block statement +#[derive(PartialEq, Debug, Clone)] +pub struct BlockHeader { + pub generic_clause: Option, + pub generic_map: Option, + pub port_clause: Option, + pub port_map: Option, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum SensitivityList { + Names(Vec>), + All, +} + +/// LRM 11.3 Process statement +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ProcessStatement { + pub postponed: bool, + pub sensitivity_list: Option>, + pub is_token: Option, + pub decl: Vec>, + pub begin_token: TokenId, + pub statements: Vec, + pub end_token: TokenId, + pub end_label_pos: Option, +} + +/// LRM 11.4 Concurrent procedure call statements +#[derive(PartialEq, Debug, Clone)] +pub struct ConcurrentProcedureCall { + pub postponed: bool, + pub call: WithTokenSpan, +} + +/// LRM 11.5 Concurrent assertion statements +#[derive(PartialEq, Debug, Clone)] +pub struct ConcurrentAssertStatement { + pub postponed: bool, + pub statement: AssertStatement, +} + +/// 11.6 Concurrent signal assignment statements +#[derive(PartialEq, Debug, Clone)] +pub struct ConcurrentSignalAssignment { + pub postponed: bool, + pub guarded: bool, + pub assignment: SignalAssignment, +} + +/// 11.7 Component instantiation statements +#[derive(PartialEq, Debug, Clone)] +pub enum InstantiatedUnit { + Component(WithTokenSpan), + Entity(WithTokenSpan, Option>), + Configuration(WithTokenSpan), +} + +impl InstantiatedUnit { + /// Returns a reference to the unit that this instantiation declares + pub fn entity_reference(&self) -> Option { + match &self { + InstantiatedUnit::Entity(name, _) => name.item.get_suffix_reference(), + InstantiatedUnit::Configuration(name) => name.item.get_suffix_reference(), + InstantiatedUnit::Component(name) => name.item.get_suffix_reference(), + } + } +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct MapAspect { + pub list: SeparatedList, +} + +impl MapAspect { + /// Returns an iterator over the formal elements of this map + pub fn formals(&self) -> impl Iterator> + '_ { + self.list.formals() + } +} + +/// 11.7 Component instantiation statements +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct InstantiationStatement { + pub unit: InstantiatedUnit, + pub generic_map: Option, + pub port_map: Option, +} + +impl InstantiationStatement { + /// Returns the reference to the entity declaring this instance + pub fn entity_reference(&self) -> Option { + self.unit.entity_reference() + } +} + +/// 11.8 Generate statements +#[derive(PartialEq, Debug, Clone)] +pub struct GenerateBody { + pub alternative_label: Option>, + pub decl: Option<(Vec>, TokenId)>, + pub statements: Vec, + pub end_token: Option, + pub end_label: Option, +} + +/// 11.8 Generate statements +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ForGenerateStatement { + pub index_name: WithDecl, + pub discrete_range: DiscreteRange, + pub generate_token: TokenId, + pub body: GenerateBody, + pub end_token: TokenId, + pub end_label_pos: Option, +} + +/// 11.8 Generate statements +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct IfGenerateStatement { + pub conds: Conditionals, + pub end_label_pos: Option, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct CaseGenerateStatement { + pub sels: Selection, + pub end_token: TokenId, + pub end_label_pos: Option, +} + +/// LRM 6.5.2 Interface Object Declarations - Mode view declarations +#[derive(PartialEq, Debug, Clone)] +pub struct ModeViewDeclaration { + pub ident: WithDecl, + pub typ: SubtypeIndication, + pub is_token: TokenId, + pub elements: Vec, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ModeViewElement { + pub names: Vec>, + pub colon_token: TokenId, + pub mode: ElementMode, +} + +#[derive(PartialEq, Debug, Clone)] +pub enum ElementMode { + Simple(WithToken), + Record(WithTokenSpan), + Array(WithTokenSpan), +} + +/// LRM 11. Concurrent statements +#[derive(PartialEq, Debug, Clone)] +pub enum ConcurrentStatement { + ProcedureCall(ConcurrentProcedureCall), + Block(BlockStatement), + Process(ProcessStatement), + Assert(ConcurrentAssertStatement), + Assignment(ConcurrentSignalAssignment), + Instance(InstantiationStatement), + ForGenerate(ForGenerateStatement), + IfGenerate(IfGenerateStatement), + CaseGenerate(CaseGenerateStatement), +} + +/// LRM 11. Concurrent statements +#[derive(PartialEq, Debug, Clone)] +pub struct LabeledConcurrentStatement { + pub label: WithDecl>, + pub statement: WithTokenSpan, +} + +/// LRM 13. Design units and their analysis +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct LibraryClause { + pub name_list: Vec>, +} + +/// Represents a token-separated list of some generic type `T` +#[derive(PartialEq, Debug, Clone)] +pub struct SeparatedList { + pub items: Vec, + pub tokens: Vec, +} + +impl Default for SeparatedList { + fn default() -> Self { + SeparatedList { + items: Vec::default(), + tokens: Vec::default(), + } + } +} + +impl SeparatedList { + /// Returns an iterator over the formal elements of this list + pub fn formals(&self) -> impl Iterator> + '_ { + self.items.iter().filter_map(|el| match &el.formal { + None => None, + Some(name) => match &name.item { + Name::Designator(desi) => Some(desi.reference.get()), + _ => None, + }, + }) + } +} + +impl SeparatedList { + pub fn single(item: T) -> SeparatedList { + SeparatedList { + items: vec![item], + tokens: vec![], + } + } +} + +/// LRM 12.4. Use clauses +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct UseClause { + pub name_list: Vec>, +} + +/// LRM 13.4 Context clauses +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ContextReference { + pub name_list: Vec>, +} + +/// LRM 13.4 Context clauses +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum ContextItem { + Use(UseClause), + Library(LibraryClause), + Context(ContextReference), +} + +/// LRM 13.4 Context clauses +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ContextDeclaration { + pub ident: WithDecl, + pub items: ContextClause, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +/// LRM 4.9 Package instantiation declaration +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct PackageInstantiation { + pub context_clause: ContextClause, + pub ident: WithDecl, + pub package_name: WithTokenSpan, + pub generic_map: Option, +} + +/// LRM 7.3 Configuration specification +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum InstantiationList { + Labels(Vec), + Others, + All, +} + +/// LRM 7.3.2 Binding indication +#[derive(PartialEq, Debug, Clone)] +pub enum EntityAspect { + Entity(WithTokenSpan, Option), + Configuration(WithTokenSpan), + Open, +} + +/// LRM 7.3.2 Binding indication +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct BindingIndication { + pub entity_aspect: Option, + pub generic_map: Option, + pub port_map: Option, +} + +/// LRM 7.3 Configuration specification +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ComponentSpecification { + pub instantiation_list: InstantiationList, + pub colon_token: TokenId, + pub component_name: WithTokenSpan, +} + +/// LRM 7.3.4 Verification unit binding indication +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct VUnitBindingIndication { + pub vunit_list: Vec>, +} + +/// LRM 7.3 Configuration specification +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ConfigurationSpecification { + pub spec: ComponentSpecification, + pub bind_ind: BindingIndication, + pub vunit_bind_inds: Vec, + pub end_token: Option, +} + +/// LRM 3.4 Configuration declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ComponentConfiguration { + pub spec: ComponentSpecification, + pub bind_ind: Option, + pub vunit_bind_inds: Vec, + pub block_config: Option, +} + +/// LRM 3.4 Configuration declarations +#[derive(PartialEq, Debug, Clone)] +pub enum ConfigurationItem { + Block(BlockConfiguration), + Component(ComponentConfiguration), +} + +/// LRM 3.4 Configuration declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct BlockConfiguration { + pub block_spec: WithTokenSpan, + pub use_clauses: Vec, + pub items: Vec, +} + +/// LRM 3.4 Configuration declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ConfigurationDeclaration { + pub context_clause: ContextClause, + pub ident: WithDecl, + pub entity_name: WithTokenSpan, + pub decl: Vec>, + pub vunit_bind_inds: Vec, + pub block_config: BlockConfiguration, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +/// LRM 3.2 Entity declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct EntityDeclaration { + pub context_clause: ContextClause, + pub ident: WithDecl, + pub generic_clause: Option, + pub port_clause: Option, + pub decl: Vec>, + pub begin_token: Option, + pub statements: Vec, + /// The `end` token from the declaration `*end* entity foo;` + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +impl EntityDeclaration { + /// The `is` token from the declaration `entity foo *is*` + pub fn is_token(&self) -> TokenId { + self.span.start_token + 2 + } +} + +/// LRM 3.3 Architecture bodies +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct ArchitectureBody { + pub context_clause: ContextClause, + pub ident: WithDecl, + pub entity_name: WithRef, + pub begin_token: TokenId, + pub decl: Vec>, + pub statements: Vec, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +impl ArchitectureBody { + /// Location of the `is` token from + /// `architecture arch of ent is` + pub fn is_token(&self) -> TokenId { + self.span.start_token + 4 + } + + /// Returns the span that encompasses the statements of this architecture, i.e. + /// ```vhdl + /// architecture arch of ent is + /// begin /* start */ + /// foo <= bar; + /// /* end*/ end arch; + /// ``` + /// Note that the `begin` and `end` tokens are **included** in the span + /// to avoid the ambiguity of an empty statement part. + pub fn statement_span(&self) -> TokenSpan { + TokenSpan::new(self.begin_token, self.end_token) + } +} + +/// LRM 4.7 Package declarations +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct PackageDeclaration { + pub context_clause: ContextClause, + pub ident: WithDecl, + pub generic_clause: Option, + pub decl: Vec>, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +/// LRM 4.8 Package bodies +#[with_token_span] +#[derive(PartialEq, Debug, Clone)] +pub struct PackageBody { + pub context_clause: ContextClause, + pub ident: WithDecl, + pub decl: Vec>, + pub end_token: TokenId, + pub end_ident_pos: Option, +} + +/// LRM 13.1 Design units +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum AnyPrimaryUnit { + /// LRM 3.2 Entity declaration + Entity(EntityDeclaration), + + /// LRM 3.4 Configuration declarations + Configuration(ConfigurationDeclaration), + + /// LRM 4.7 Package declarations + Package(PackageDeclaration), + + /// LRM 4.9 Package instatiation declaration + PackageInstance(PackageInstantiation), + + /// LRM 13.4 Context clauses + Context(ContextDeclaration), +} + +/// LRM 13.1 Design units +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum AnySecondaryUnit { + /// LRM 3.3 Architecture bodies + Architecture(ArchitectureBody), + + /// LRM 4.8 Package bodies + PackageBody(PackageBody), +} + +pub type ContextClause = Vec; + +/// LRM 13.1 Design units +#[derive(PartialEq, Debug, Clone, TokenSpan)] +pub enum AnyDesignUnit { + Primary(AnyPrimaryUnit), + Secondary(AnySecondaryUnit), +} + +impl AnyDesignUnit { + pub fn is_entity(&self) -> bool { + matches!(self, AnyDesignUnit::Primary(AnyPrimaryUnit::Entity(_))) + } +} + +#[derive(PartialEq, Debug, Clone, Default)] +pub struct DesignFile { + pub design_units: Vec<(Vec, AnyDesignUnit)>, +} diff --git a/vhdl_lang/src/ast/any_design_unit.rs b/vhdl_lang/src/ast/any_design_unit.rs new file mode 100644 index 0000000..8fcbbb8 --- /dev/null +++ b/vhdl_lang/src/ast/any_design_unit.rs @@ -0,0 +1,232 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2020, Olof Kraigher olof.kraigher@gmail.com + +use super::*; + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub enum PrimaryKind { + Entity, + Configuration, + Package, + PackageInstance, + Context, +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub enum SecondaryKind { + Architecture, + PackageBody, +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub enum AnyKind { + Primary(PrimaryKind), + Secondary(SecondaryKind), +} + +/// Stores a design unit's name and, for secondary units, +/// the name of its associated primary unit. +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub enum UnitKey { + Primary(Symbol), + Secondary(Symbol, Symbol), +} + +/// Identifies a design unit. +/// +/// Additionally, a `UnitId` specifies a unit's name, kind, library, +/// and, for secondary units, its associated primary unit. +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub struct UnitId { + library_name: Symbol, + kind: AnyKind, + key: UnitKey, +} + +impl UnitId { + pub fn primary(library_name: &Symbol, kind: PrimaryKind, name: &Symbol) -> UnitId { + UnitId { + library_name: library_name.clone(), + kind: AnyKind::Primary(kind), + key: UnitKey::Primary(name.clone()), + } + } + + pub fn secondary( + library_name: &Symbol, + kind: SecondaryKind, + primary_name: &Symbol, + name: &Symbol, + ) -> UnitId { + UnitId { + library_name: library_name.clone(), + kind: AnyKind::Secondary(kind), + key: UnitKey::Secondary(primary_name.clone(), name.clone()), + } + } + + pub fn package(library_name: &Symbol, name: &Symbol) -> UnitId { + Self::primary(library_name, PrimaryKind::Package, name) + } + + pub fn library_name(&self) -> &Symbol { + &self.library_name + } + + /// For a secondary unit, returns the name of the associated primary unit; + /// for a primary unit, returns its own name. + pub fn primary_name(&self) -> &Symbol { + match self.key { + UnitKey::Primary(ref name) => name, + UnitKey::Secondary(ref name, _) => name, + } + } + + pub fn secondary_name(&self) -> Option<&Symbol> { + match self.key { + UnitKey::Primary(_) => None, + UnitKey::Secondary(_, ref name) => Some(name), + } + } +} + +pub trait HasUnitId { + fn unit_id(&self) -> &UnitId; + fn kind(&self) -> AnyKind { + self.unit_id().kind + } + fn key(&self) -> &UnitKey { + &self.unit_id().key + } + + fn secondary_kind(&self) -> Option { + match self.unit_id().kind { + AnyKind::Secondary(kind) => Some(kind), + AnyKind::Primary(..) => None, + } + } + + fn describe(&self) -> String { + match self.key() { + UnitKey::Primary(name) => format!("{} '{}'", self.kind().describe(), name), + UnitKey::Secondary(primary_name, name) => match self.secondary_kind().unwrap() { + SecondaryKind::Architecture => format!( + "{} '{}' of '{}'", + self.kind().describe(), + name, + primary_name + ), + SecondaryKind::PackageBody => format!("{} '{}'", self.kind().describe(), name), + }, + } + } +} + +impl HasUnitId for UnitId { + fn unit_id(&self) -> &UnitId { + self + } +} + +macro_rules! delegate_primary { + ($primary:expr, $unit:ident, $block:expr) => { + match $primary { + AnyPrimaryUnit::Entity($unit) => $block, + AnyPrimaryUnit::Package($unit) => $block, + AnyPrimaryUnit::PackageInstance($unit) => $block, + AnyPrimaryUnit::Context($unit) => $block, + AnyPrimaryUnit::Configuration($unit) => $block, + } + }; +} + +macro_rules! delegate_secondary { + ($primary:expr, $unit:ident, $block:expr) => { + match $primary { + AnySecondaryUnit::Architecture($unit) => $block, + AnySecondaryUnit::PackageBody($unit) => $block, + } + }; +} + +macro_rules! delegate_any { + ($primary:expr, $unit:ident, $block:expr) => { + match $primary { + AnyDesignUnit::Primary($unit) => $block, + AnyDesignUnit::Secondary($unit) => $block, + } + }; +} + +impl AnyKind { + pub fn describe(&self) -> &str { + match self { + AnyKind::Primary(kind) => kind.describe(), + AnyKind::Secondary(kind) => kind.describe(), + } + } +} + +impl PrimaryKind { + pub fn kind_of(unit: &AnyPrimaryUnit) -> PrimaryKind { + match unit { + AnyPrimaryUnit::Entity(..) => PrimaryKind::Entity, + AnyPrimaryUnit::Configuration(..) => PrimaryKind::Configuration, + AnyPrimaryUnit::Package(..) => PrimaryKind::Package, + AnyPrimaryUnit::PackageInstance(..) => PrimaryKind::PackageInstance, + AnyPrimaryUnit::Context(..) => PrimaryKind::Context, + } + } + + pub fn describe(&self) -> &str { + match self { + PrimaryKind::Entity => "entity", + PrimaryKind::Configuration => "configuration", + PrimaryKind::Package => "package", + PrimaryKind::PackageInstance => "package instance", + PrimaryKind::Context => "context", + } + } +} + +impl SecondaryKind { + pub fn kind_of(unit: &AnySecondaryUnit) -> SecondaryKind { + match unit { + AnySecondaryUnit::Architecture(..) => SecondaryKind::Architecture, + AnySecondaryUnit::PackageBody(..) => SecondaryKind::PackageBody, + } + } + + pub fn describe(&self) -> &str { + match self { + SecondaryKind::Architecture => "architecture", + SecondaryKind::PackageBody => "package body", + } + } +} + +impl AnyDesignUnit { + pub fn as_primary_mut(&mut self) -> Option<&mut AnyPrimaryUnit> { + if let AnyDesignUnit::Primary(unit) = self { + Some(unit) + } else { + None + } + } +} + +/// Upper case first letter +pub fn capitalize(string: &str) -> String { + let mut result = String::with_capacity(string.len()); + let mut chars = string.chars(); + if let Some(chr) = chars.next() { + result.push(chr.to_ascii_uppercase()); + } + for chr in chars { + result.push(chr); + } + result +} diff --git a/vhdl_lang/src/ast/ast_span.rs b/vhdl_lang/src/ast/ast_span.rs new file mode 100644 index 0000000..59531da --- /dev/null +++ b/vhdl_lang/src/ast/ast_span.rs @@ -0,0 +1,83 @@ +use crate::ast::token_range::WithTokenSpan; +use crate::ast::{LabeledConcurrentStatement, LabeledSequentialStatement, WithDecl}; +use crate::{HasTokenSpan, TokenId}; +use vhdl_lang::ast::WithToken; +use vhdl_lang::TokenSpan; + +impl HasTokenSpan for TokenId { + fn get_start_token(&self) -> TokenId { + *self + } + + fn get_end_token(&self) -> TokenId { + *self + } +} + +impl HasTokenSpan for TokenSpan { + fn get_start_token(&self) -> TokenId { + self.start_token + } + + fn get_end_token(&self) -> TokenId { + self.end_token + } +} + +impl HasTokenSpan for WithToken { + fn get_start_token(&self) -> TokenId { + self.token + } + + fn get_end_token(&self) -> TokenId { + self.token + } +} + +impl HasTokenSpan for WithDecl> { + fn get_start_token(&self) -> TokenId { + self.tree.get_start_token() + } + + fn get_end_token(&self) -> TokenId { + self.tree.get_end_token() + } +} + +impl HasTokenSpan for WithTokenSpan { + fn get_start_token(&self) -> TokenId { + self.span.start_token + } + + fn get_end_token(&self) -> TokenId { + self.span.end_token + } +} + +impl HasTokenSpan for LabeledConcurrentStatement { + fn get_start_token(&self) -> TokenId { + if let Some(label) = &self.label.tree { + label.token + } else { + self.statement.span.start_token + } + } + + fn get_end_token(&self) -> TokenId { + self.statement.span.end_token + } +} + +impl HasTokenSpan for LabeledSequentialStatement { + fn get_start_token(&self) -> TokenId { + if let Some(label) = &self.label.tree { + label.token + } else { + self.statement.span.start_token + } + } + + fn get_end_token(&self) -> TokenId { + self.statement.span.end_token + } +} diff --git a/vhdl_lang/src/ast/display.rs b/vhdl_lang/src/ast/display.rs new file mode 100644 index 0000000..30dd595 --- /dev/null +++ b/vhdl_lang/src/ast/display.rs @@ -0,0 +1,2343 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +//! Implementation of Display + +use super::*; +use itertools::Itertools; +use std::fmt::{Display, Formatter, Result}; + +impl Display for WithTokenSpan { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", &self.item) + } +} + +impl Display for WithDecl { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", &self.tree) + } +} + +impl Display for WithToken +where + T: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", &self.item) + } +} + +impl Display for BaseSpecifier { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + BaseSpecifier::B => write!(f, "b"), + BaseSpecifier::O => write!(f, "o"), + BaseSpecifier::X => write!(f, "x"), + BaseSpecifier::UB => write!(f, "ub"), + BaseSpecifier::UO => write!(f, "uo"), + BaseSpecifier::UX => write!(f, "ux"), + BaseSpecifier::SB => write!(f, "sb"), + BaseSpecifier::SO => write!(f, "so"), + BaseSpecifier::SX => write!(f, "sx"), + BaseSpecifier::D => write!(f, "d"), + } + } +} + +impl Display for Operator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Operator::And => write!(f, "and"), + Operator::Or => write!(f, "or"), + Operator::Nand => write!(f, "nand"), + Operator::Nor => write!(f, "nor"), + Operator::Xor => write!(f, "xor"), + Operator::Xnor => write!(f, "xnor"), + Operator::EQ => write!(f, "="), + Operator::NE => write!(f, "/="), + Operator::LT => write!(f, "<"), + Operator::LTE => write!(f, "<="), + Operator::GT => write!(f, ">"), + Operator::GTE => write!(f, ">="), + Operator::QueEQ => write!(f, "?="), + Operator::QueNE => write!(f, "?/="), + Operator::QueLT => write!(f, "?<"), + Operator::QueLTE => write!(f, "?<="), + Operator::QueGT => write!(f, "?>"), + Operator::QueGTE => write!(f, "?>="), + Operator::SLL => write!(f, "sll"), + Operator::SRL => write!(f, "srl"), + Operator::SLA => write!(f, "sla"), + Operator::SRA => write!(f, "sra"), + Operator::ROL => write!(f, "rol"), + Operator::ROR => write!(f, "ror"), + Operator::Plus => write!(f, "+"), + Operator::Minus => write!(f, "-"), + Operator::Concat => write!(f, "&"), + Operator::Times => write!(f, "*"), + Operator::Div => write!(f, "/"), + Operator::Mod => write!(f, "mod"), + Operator::Rem => write!(f, "rem"), + Operator::Pow => write!(f, "**"), + Operator::Abs => write!(f, "abs"), + Operator::Not => write!(f, "not"), + Operator::QueQue => write!(f, "??"), + } + } +} + +impl Display for AttributeName { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", self.name)?; + if let Some(ref signature) = self.signature { + write!(f, "{signature}")?; + } + write!(f, "'{}", self.attr)?; + if let Some(ref expr) = self.expr { + write!(f, "({expr})") + } else { + Ok(()) + } + } +} + +impl Display for AttributeDesignator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + AttributeDesignator::Ident(sym) => write!(f, "{sym}"), + AttributeDesignator::Range(r) => write!(f, "{r}"), + AttributeDesignator::Type(t) => write!(f, "{t}"), + AttributeDesignator::Ascending => write!(f, "ascending"), + AttributeDesignator::Left => write!(f, "left"), + AttributeDesignator::Right => write!(f, "right"), + AttributeDesignator::Low => write!(f, "low"), + AttributeDesignator::High => write!(f, "high"), + AttributeDesignator::Length => write!(f, "length"), + AttributeDesignator::Image => write!(f, "image"), + AttributeDesignator::Value => write!(f, "value"), + AttributeDesignator::Pos => write!(f, "pos"), + AttributeDesignator::Val => write!(f, "val"), + AttributeDesignator::Succ => write!(f, "succ"), + AttributeDesignator::Pred => write!(f, "pred"), + AttributeDesignator::LeftOf => write!(f, "leftof"), + AttributeDesignator::RightOf => write!(f, "rightof"), + AttributeDesignator::Signal(s) => write!(f, "{s}"), + AttributeDesignator::SimpleName => write!(f, "simple_name"), + AttributeDesignator::InstanceName => write!(f, "instance_name"), + AttributeDesignator::PathName => write!(f, "path_name"), + AttributeDesignator::Converse => write!(f, "converse"), + } + } +} + +impl Display for SignalAttribute { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + SignalAttribute::Delayed => write!(f, "delayed"), + SignalAttribute::Stable => write!(f, "stable"), + SignalAttribute::Quiet => write!(f, "quiet"), + SignalAttribute::Transaction => write!(f, "transaction"), + SignalAttribute::Event => write!(f, "event"), + SignalAttribute::Active => write!(f, "active"), + SignalAttribute::LastEvent => write!(f, "last_event"), + SignalAttribute::LastActive => write!(f, "last_active"), + SignalAttribute::LastValue => write!(f, "last_value"), + SignalAttribute::Driving => write!(f, "driving"), + SignalAttribute::DrivingValue => write!(f, "driving_value"), + } + } +} + +impl Display for TypeAttribute { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + TypeAttribute::Subtype => write!(f, "subtype"), + TypeAttribute::Element => write!(f, "element"), + } + } +} + +impl Display for RangeAttribute { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + RangeAttribute::Range => write!(f, "range"), + RangeAttribute::ReverseRange => write!(f, "reverse_range"), + } + } +} + +impl Display for EntityClass { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + EntityClass::Entity => write!(f, "entity"), + EntityClass::Architecture => write!(f, "architecture"), + EntityClass::Configuration => write!(f, "ponfiguration"), + EntityClass::Procedure => write!(f, "procedure"), + EntityClass::Function => write!(f, "function"), + EntityClass::Package => write!(f, "package"), + EntityClass::Type => write!(f, "type"), + EntityClass::Subtype => write!(f, "subtype"), + EntityClass::Constant => write!(f, "constant"), + EntityClass::Signal => write!(f, "signal"), + EntityClass::Variable => write!(f, "variable"), + EntityClass::Component => write!(f, "component"), + EntityClass::Label => write!(f, "label"), + EntityClass::Literal => write!(f, "literal"), + EntityClass::Units => write!(f, "units"), + EntityClass::File => write!(f, "file"), + } + } +} + +impl Display for ExternalObjectClass { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ExternalObjectClass::Constant => write!(f, "constant"), + ExternalObjectClass::Signal => write!(f, "signal"), + ExternalObjectClass::Variable => write!(f, "variable"), + } + } +} + +impl Display for ExternalPath { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ExternalPath::Package(ref path) => { + write!(f, "@{path}") + } + ExternalPath::Absolute(ref path) => { + write!(f, ".{path}") + } + ExternalPath::Relative(ref path, ref up_levels) => { + write!(f, "{}{}", "^.".repeat(*up_levels), path) + } + } + } +} + +impl Display for ExternalName { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "<< {} {} : {} >>", self.class, self.path, self.subtype) + } +} + +impl Display for Name { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Name::Designator(designator) => write!(f, "{designator}"), + Name::Selected(ref prefix, ref designator) => write!(f, "{prefix}.{designator}"), + Name::SelectedAll(ref prefix) => write!(f, "{prefix}.all"), + Name::Slice(ref prefix, ref drange) => write!(f, "{prefix}({drange})"), + Name::Attribute(ref attr) => write!(f, "{attr}"), + Name::CallOrIndexed(ref fcall) => write!(f, "{fcall}"), + Name::External(ref ename) => write!(f, "{ename}"), + } + } +} + +impl Display for CallOrIndexed { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", self.name)?; + let mut first = true; + for param in &self.parameters.items { + if first { + write!(f, "({param}")?; + } else { + write!(f, ", {param}")?; + } + first = false; + } + if !first { + write!(f, ")") + } else { + Ok(()) + } + } +} + +impl Display for AttributeDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "attribute {} : {};", self.ident, self.type_mark) + } +} + +impl Display for Choice { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Choice::Expression(ref expr) => write!(f, "{expr}"), + Choice::DiscreteRange(ref drange) => write!(f, "{drange}"), + Choice::Others => write!(f, "others"), + } + } +} + +impl Display for ElementAssociation { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ElementAssociation::Positional(ref expr) => { + write!(f, "{expr}") + } + ElementAssociation::Named(ref choices, ref expr) => { + let mut first = true; + for choice in choices { + if first { + write!(f, "{choice}")?; + } else { + write!(f, " | {choice}")?; + } + first = false; + } + write!(f, " => {expr}") + } + } + } +} + +impl Display for ActualPart { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ActualPart::Expression(ref expr) => write!(f, "{expr}"), + ActualPart::Open => write!(f, "open"), + } + } +} + +impl Display for AssociationElement { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if let Some(ref formal) = self.formal { + write!(f, "{formal} => ")?; + } + write!(f, "{}", self.actual) + } +} + +impl Display for AbstractLiteral { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + AbstractLiteral::Integer(val) => write!(f, "{val}"), + AbstractLiteral::Real(val) => write!(f, "{val}"), + } + } +} + +impl Display for BitString { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if let Some(length) = self.length { + write!(f, "{length}")?; + } + write!(f, "{}\"{}\"", self.base, self.value) + } +} + +impl Display for PhysicalLiteral { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{} {}", self.value, self.unit) + } +} + +impl Display for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Literal::String(ref val) => write!(f, "\"{val}\""), + Literal::BitString(ref val) => write!(f, "{val}"), + Literal::Character(byte) => write!(f, "'{}'", *byte as char), + Literal::AbstractLiteral(ref val) => write!(f, "{val}"), + Literal::Physical(ref val) => write!(f, "{val}"), + Literal::Null => write!(f, "null"), + } + } +} + +impl Display for Allocator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Allocator::Qualified(ref qexpr) => write!(f, "{qexpr}"), + Allocator::Subtype(ref subtype) => write!(f, "{subtype}"), + } + } +} + +impl Display for QualifiedExpression { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self.expr.item { + Expression::Aggregate(..) => write!(f, "{}'{}", self.type_mark, self.expr), + _ => write!(f, "{}'{}", self.type_mark, self.expr), + } + } +} + +impl Display for Expression { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Expression::Binary(ref op, ref lhs, ref rhs) => { + // Add parentheses as necessary to satisfy order of precedence. + let precedence = op.item.item.binary_precedence().unwrap(); + match &lhs.item { + Expression::Binary(op, ..) => { + if precedence <= op.item.item.binary_precedence().unwrap() { + write!(f, "{lhs}")?; + } else { + write!(f, "({lhs})")?; + } + } + Expression::Unary(op, ..) => { + if precedence <= op.item.item.unary_precedence().unwrap() { + write!(f, "{lhs}")?; + } else { + write!(f, "({lhs})")?; + } + } + _ => write!(f, "{lhs}")?, + } + write!(f, " {op} ")?; + match &rhs.item { + Expression::Binary(op, ..) => { + if precedence < op.item.item.binary_precedence().unwrap() { + write!(f, "{rhs}") + } else { + write!(f, "({rhs})") + } + } + _ => write!(f, "{rhs}"), + } + } + Expression::Unary(ref op, ref expr) => { + // Add parentheses as necessary to satisfy order of precedence. + let precedence = op.item.item.unary_precedence().unwrap(); + if matches!(op.item.item, Operator::Minus | Operator::Plus) { + write!(f, "{op}")?; + } else { + write!(f, "{op} ")?; + } + match &expr.item { + // Binary operators having precedence over unary ones is + // confusing, so always add parentheses. + Expression::Binary(..) => { + write!(f, "({expr})") + } + // Chained unary operators are always left to right, but + // chained operators with the same precedence are + // parenthesized for clarity. + Expression::Unary(op, ..) => { + if precedence != op.item.item.unary_precedence().unwrap() { + write!(f, "{expr}") + } else { + write!(f, "({expr})") + } + } + _ => write!(f, "{expr}"), + } + } + Expression::Aggregate(ref assocs) => { + let mut first = true; + for assoc in assocs { + if first { + write!(f, "({assoc}")?; + } else { + write!(f, ", {assoc}")?; + } + first = false; + } + if !first { + write!(f, ")") + } else { + Ok(()) + } + } + Expression::Qualified(ref qexpr) => write!(f, "{qexpr}"), + Expression::Name(ref name) => write!(f, "{name}"), + Expression::Literal(ref literal) => write!(f, "{literal}"), + Expression::New(ref alloc) => write!(f, "new {alloc}"), + Expression::Parenthesized(expr) => write!(f, "({expr})"), + } + } +} + +impl Display for Direction { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Direction::Ascending => write!(f, "to"), + Direction::Descending => write!(f, "downto"), + } + } +} + +impl Display for DiscreteRange { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + DiscreteRange::Discrete(ref name, ref range) => { + write!(f, "{name}")?; + match range { + Some(ref range) => write!(f, " range {range}"), + None => Ok(()), + } + } + DiscreteRange::Range(ref range) => { + write!(f, "{range}") + } + } + } +} + +impl Display for RangeConstraint { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "{} {} {}", + self.left_expr, self.direction, self.right_expr, + ) + } +} + +impl Display for Range { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Range::Range(ref constraint) => write!(f, "{constraint}"), + Range::Attribute(ref attr) => write!(f, "{attr}"), + } + } +} + +impl Display for ElementConstraint { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}{}", self.ident, self.constraint) + } +} + +impl Display for SubtypeConstraint { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + SubtypeConstraint::Range(ref range) => { + write!(f, " range {range}") + } + SubtypeConstraint::Array(ref dranges, ref constraint) => { + write!(f, "(")?; + let mut first = true; + for drange in dranges { + if first { + write!(f, "{drange}")?; + } else { + write!(f, ", {drange}")?; + } + first = false; + } + if first { + write!(f, "open")?; + } + match constraint { + Some(ref constraint) => write!(f, "){constraint}"), + None => write!(f, ")"), + } + } + SubtypeConstraint::Record(constraints) => { + write!(f, "(")?; + let mut first = true; + for constraint in constraints { + if first { + write!(f, "{constraint}")?; + } else { + write!(f, ", {constraint}")?; + } + first = false; + } + write!(f, ")") + } + } + } +} + +impl Display for RecordElementResolution { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{} {}", self.ident, self.resolution) + } +} + +impl Display for ResolutionIndication { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ResolutionIndication::FunctionName(ref name) => { + write!(f, "{name}") + } + ResolutionIndication::ArrayElement(ref name) => { + write!(f, "({name})") + } + ResolutionIndication::Record(elem_resolutions) => { + let mut first = true; + for elem_resolution in &elem_resolutions.item { + if first { + write!(f, "({elem_resolution}")?; + } else { + write!(f, ", {elem_resolution}")?; + } + first = false; + } + if !first { + write!(f, ")") + } else { + Ok(()) + } + } + } + } +} + +impl Display for SubtypeIndication { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if let Some(resolution) = &self.resolution { + write!(f, "{resolution} ")?; + } + write!(f, "{}", self.type_mark)?; + match self.constraint { + Some(ref constraint) => write!(f, "{constraint}"), + None => Ok(()), + } + } +} + +impl Display for ArrayIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ArrayIndex::IndexSubtypeDefintion(ref type_mark) => { + write!(f, "{type_mark} range <>") + } + ArrayIndex::Discrete(ref range) => { + write!(f, "{range}") + } + } + } +} + +impl Display for ElementDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "{} : {}", + self.idents.iter().map(|item| format!("{item}")).join(", "), + self.subtype + ) + } +} + +impl Display for Designator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Designator::Identifier(ref sym) => write!(f, "{sym}"), + Designator::OperatorSymbol(ref op) => write!(f, "\"{op}\""), + Designator::Character(byte) => write!(f, "'{}'", iso_8859_1_to_utf8(&[*byte])), + Designator::Anonymous(idx) => write!(f, "", idx), + } + } +} + +impl Display for WithRef { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", &self.item) + } +} + +impl Display for AliasDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "alias {}", self.designator)?; + if let Some(ref subtype_indication) = self.subtype_indication { + write!(f, " : {subtype_indication}")?; + } + write!(f, " is {}", self.name)?; + match self.signature { + Some(ref signature) => write!(f, "{signature};"), + None => write!(f, ";"), + } + } +} + +impl Display for EnumerationLiteral { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + EnumerationLiteral::Identifier(ref sym) => write!(f, "{sym}"), + EnumerationLiteral::Character(byte) => write!(f, "'{}'", *byte as char), + } + } +} + +impl Display for TypeDefinition { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + TypeDefinition::Enumeration(ref enum_literals) => { + write!(f, " is (")?; + let mut first = true; + for literal in enum_literals { + if first { + write!(f, "{literal}")?; + } else { + write!(f, ", {literal}")?; + } + first = false; + } + write!(f, ");") + } + TypeDefinition::Numeric(ref constraint) => { + write!(f, " is range {constraint};") + } + TypeDefinition::Physical(ref physical) => { + writeln!( + f, + " is range {} units\n {};", + physical.range, physical.primary_unit + )?; + for (ident, literal) in &physical.secondary_units { + writeln!(f, " {ident} = {literal};")?; + } + write!(f, "end units;") + } + TypeDefinition::Array(ref indexes, _, ref subtype_indication) => { + write!(f, " is array (")?; + let mut first = true; + for index in indexes { + if first { + write!(f, "{index}")?; + } else { + write!(f, ", {index}")?; + } + first = false; + } + write!(f, ") of {subtype_indication};") + } + TypeDefinition::Record(ref elements) => { + writeln!(f, " is record")?; + for element in elements { + writeln!(f, " {element};")?; + } + write!(f, "end record;") + } + TypeDefinition::Access(ref subtype_indication) => { + write!(f, " is access {subtype_indication};") + } + TypeDefinition::Incomplete(..) => { + write!(f, ";") + } + TypeDefinition::File(ref type_mark) => { + write!(f, " is file of {type_mark};") + } + TypeDefinition::Protected(..) => { + // Not used: items + write!(f, " is protected") + } + TypeDefinition::ProtectedBody(..) => { + // Not used: type_reference, decl + write!(f, " is protected body") + } + TypeDefinition::Subtype(ref subtype_indication) => { + write!(f, " is {subtype_indication};") + } + } + } +} + +impl Display for TypeDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match &self.def { + TypeDefinition::Subtype(..) => write!(f, "subtype")?, + _ => write!(f, "type")?, + } + write!(f, " {}{}", self.ident, self.def) + } +} + +impl Display for ObjectClass { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ObjectClass::Signal => write!(f, "signal"), + ObjectClass::Constant => write!(f, "constant"), + ObjectClass::Variable => write!(f, "variable"), + ObjectClass::SharedVariable => write!(f, "shared variable"), + } + } +} + +impl Display for ObjectDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "{} {} : {}", + self.class, + self.idents + .iter() + .map(|ident| format!("{}", ident)) + .join(", "), + self.subtype_indication, + )?; + match self.expression { + Some(ref expr) => write!(f, " := {expr};"), + None => write!(f, ";"), + } + } +} + +impl Display for FileDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "file {} : {}", + self.idents + .iter() + .map(|ident| format!("{ident}")) + .join(", "), + self.subtype_indication + )?; + if let Some(ref expr) = self.open_info { + write!(f, " open {}", expr.1)?; + } + match self.file_name { + Some(ref expr) => write!(f, " is {};", expr.1), + None => write!(f, ";"), + } + } +} + +impl Display for SubprogramDesignator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + SubprogramDesignator::Identifier(ref sym) => write!(f, "{sym}"), + SubprogramDesignator::OperatorSymbol(ref latin1) => write!(f, "\"{latin1}\""), + } + } +} + +impl Display for ProcedureSpecification { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "procedure {}", self.designator)?; + if let Some(parameter_list) = &self.parameter_list { + let mut first = true; + for param in ¶meter_list.items { + if first { + write!(f, "(\n {param}")?; + } else { + write!(f, ";\n {param}")?; + } + first = false; + } + if !first { + write!(f, "\n)") + } else { + Ok(()) + } + } else { + Ok(()) + } + } +} + +impl Display for FunctionSpecification { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if !self.pure { + write!(f, "impure ")?; + } + write!(f, "function {}", self.designator)?; + let mut first = true; + if let Some(parameter_list) = &self.parameter_list { + for param in ¶meter_list.items { + if first { + write!(f, "(\n {param}")?; + } else { + write!(f, ";\n {param}")?; + } + first = false; + } + if !first { + write!(f, "\n)")?; + } + } + write!(f, " return {}", self.return_type) + } +} + +impl Display for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Signature::Function(ref args, ref ret) => { + write!(f, "[")?; + let mut first = true; + for arg in args { + if first { + write!(f, "{arg}")?; + } else { + write!(f, ", {arg}")?; + } + first = false; + } + if first { + write!(f, "return {ret}]") + } else { + write!(f, " return {ret}]") + } + } + Signature::Procedure(ref args) => { + write!(f, "[")?; + let mut first = true; + for arg in args { + if first { + write!(f, "{arg}")?; + } else { + write!(f, ", {arg}")?; + } + first = false; + } + write!(f, "]") + } + } + } +} + +impl Display for SubprogramDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{};", self.specification) + } +} + +impl Display for SubprogramSpecification { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + SubprogramSpecification::Procedure(ref procedure) => write!(f, "{procedure}"), + SubprogramSpecification::Function(ref function) => write!(f, "{function}"), + } + } +} + +impl Display for SubprogramInstantiation { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self.kind { + SubprogramKind::Function => write!(f, "function ")?, + SubprogramKind::Procedure => write!(f, "procedure ")?, + }; + write!(f, "{}", self.ident)?; + write!(f, " is new ")?; + write!(f, "{}", self.subprogram_name)?; + if let Some(signature) = &self.signature { + write!(f, " {}", signature)?; + } + if let Some(generic_map) = &self.generic_map { + writeln!(f, " generic map (")?; + write_separated_list(&generic_map.list, f, ",")?; + write!(f, "\n)")?; + } + Ok(()) + } +} + +fn write_separated_list( + list: &SeparatedList, + f: &mut Formatter<'_>, + separator: &str, +) -> Result { + let mut first = true; + for assoc in &list.items { + if !first { + writeln!(f, "{separator}")?; + } + write!(f, " {assoc}")?; + first = false; + } + Ok(()) +} + +impl Display for InterfaceFileDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "file {} : {}", + self.idents + .iter() + .map(|ident| format!("{ident}")) + .join(", "), + self.subtype_indication + ) + } +} + +impl Display for ModeIndication { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + ModeIndication::Simple(indication) => write!(f, "{indication}"), + ModeIndication::View(view) => write!(f, "{view}"), + } + } +} + +impl Display for SimpleModeIndication { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if let Some(mode) = &self.mode { + write!(f, "{mode} ")?; + } + write!(f, "{}", self.subtype_indication)?; + if self.bus { + write!(f, " bus")?; + } + if let Some(expr) = &self.expression { + write!(f, " := {expr}")?; + } + Ok(()) + } +} + +impl Display for ModeViewIndication { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "view ")?; + match self.kind { + ModeViewIndicationKind::Array => write!(f, "({})", self.name)?, + ModeViewIndicationKind::Record => write!(f, "{}", self.name)?, + } + if let Some((_, typ)) = &self.subtype_indication { + write!(f, " of {typ}")?; + } + Ok(()) + } +} + +impl Display for InterfaceObjectDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self.list_type { + InterfaceType::Port => { + write!( + f, + "{} : {}", + self.idents.iter().map(|el| format!("{el}")).join(", "), + self.mode + ) + } + InterfaceType::Generic => { + write!( + f, + "{} : {}", + self.idents.iter().map(|el| format!("{el}")).join(", "), + self.mode + ) + } + InterfaceType::Parameter => { + if let ModeIndication::Simple(mode) = &self.mode { + write!( + f, + "{} {} : {}", + mode.class, + self.idents.iter().map(|el| format!("{el}")).join(", "), + self.mode + ) + } else { + write!( + f, + "{} : {}", + self.idents.iter().map(|el| format!("{el}")).join(", "), + self.mode + ) + } + } + } + } +} + +impl Display for SubprogramDefault { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + SubprogramDefault::Name(ref name) => write!(f, "{name}"), + SubprogramDefault::Box => write!(f, "<>"), + } + } +} + +impl Display for InterfacePackageDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "package {} is new {}\n generic map (", + self.ident, self.package_name + )?; + match &self.generic_map.item { + InterfacePackageGenericMapAspect::Map(assoc_list) => { + let mut first = true; + for assoc in &assoc_list.items { + if first { + write!(f, "\n {assoc}")?; + } else { + write!(f, ",\n {assoc}")?; + } + first = false; + } + write!(f, "\n )") + } + InterfacePackageGenericMapAspect::Box => write!(f, "<>)"), + InterfacePackageGenericMapAspect::Default => write!(f, "default)"), + } + } +} + +impl Display for InterfaceDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + InterfaceDeclaration::Object(ref decl) => write!(f, "{decl}"), + InterfaceDeclaration::File(ref decl) => write!(f, "{decl}"), + InterfaceDeclaration::Type(ref ident) => write!(f, "type {ident}"), + InterfaceDeclaration::Subprogram(ref decl) => { + write!(f, "{}", decl.specification)?; + match &decl.default { + Some(ref default) => write!(f, " is {default}"), + None => Ok(()), + } + } + InterfaceDeclaration::Package(ref decl) => write!(f, "{decl}"), + } + } +} + +impl Display for Mode { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Mode::In => write!(f, "in"), + Mode::Out => write!(f, "out"), + Mode::InOut => write!(f, "inout"), + Mode::Buffer => write!(f, "buffer"), + Mode::Linkage => write!(f, "linkage"), + } + } +} + +impl Display for ComponentDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "component {}", self.ident)?; + + if let Some(generic_list) = &self.generic_list { + let mut first = true; + for generic in &generic_list.items { + if first { + write!(f, "\n generic (\n {generic}")?; + } else { + write!(f, ";\n {generic}")?; + } + first = false; + } + if !first { + write!(f, "\n );")?; + } + } + + if let Some(port_list) = &self.port_list { + let mut first = true; + for generic in &port_list.items { + if first { + write!(f, "\n port (\n {generic}")?; + } else { + write!(f, ";\n {generic}")?; + } + first = false; + } + if !first { + write!(f, "\n );")?; + } + } + write!(f, "\nend component;") + } +} + +impl Display for ForGenerateStatement { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // Not used: body + write!( + f, + "for {} in {} generate", + self.index_name, self.discrete_range, + ) + } +} + +impl Display for ContextDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // Not used: items + write!(f, "context {}", self.ident) + } +} + +impl Display for PackageInstantiation { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // Not used: context_clause + write!(f, "package {} is new {}", self.ident, self.package_name)?; + if let Some(assoc_list) = &self.generic_map { + let mut first = true; + for assoc in &assoc_list.list.items { + if first { + write!(f, "\n generic map (\n {assoc}")?; + } else { + write!(f, ",\n {assoc}")?; + } + first = false; + } + if !first { + write!(f, "\n )")?; + } + } + write!(f, ";") + } +} + +impl Display for ConfigurationDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // Not used: context_clause, decl, block_config, vunit_bind_inds + write!(f, "configuration {} of {}", self.ident, self.entity_name) + } +} + +impl Display for EntityDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // Not used: context_clause, decl, statements + write!(f, "entity {} is", self.ident)?; + + if let Some(generic_clause) = &self.generic_clause { + let mut first = true; + for generic in &generic_clause.items { + if first { + write!(f, "\n generic (\n {generic}")?; + } else { + write!(f, ";\n {generic}")?; + } + first = false; + } + if !first { + write!(f, "\n );")?; + } + } + + if let Some(port_clause) = &self.port_clause { + let mut first = true; + for port in &port_clause.items { + if first { + write!(f, "\n port (\n {port}")?; + } else { + write!(f, ";\n {port}")?; + } + first = false; + } + if !first { + write!(f, "\n );")?; + } + } + + write!(f, "\nend entity;") + } +} + +impl Display for PackageDeclaration { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // Not used: context_clause, decl + if let Some(generic_clause) = &self.generic_clause { + write!(f, "package {} is", self.ident)?; + + let mut first = true; + for generic in &generic_clause.items { + if first { + write!(f, "\n generic (\n {generic}")?; + } else { + write!(f, ";\n {generic}")?; + } + first = false; + } + if !first { + write!(f, "\n );") + } else { + Ok(()) + } + } else { + write!(f, "package {}", self.ident) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::test::Code; + use assert_matches::assert_matches; + + pub fn assert_format_eq(code: &str, res: &str, code_fun: F) + where + F: FnOnce(&Code) -> R, + { + assert_eq!(format!("{}", code_fun(&Code::new(code))), res); + } + + pub fn assert_format(code: &str, code_fun: F) + where + F: FnOnce(&Code) -> R, + { + assert_format_eq(code, code, code_fun); + } + + #[test] + fn test_selected_name_single() { + assert_format("foo", Code::name); + } + + #[test] + fn test_selected_name_multiple() { + assert_format("foo.bar.baz", Code::name); + } + + #[test] + fn test_name_operator_symbol() { + assert_format("\"+\"", Code::name); + } + + #[test] + fn test_name_character() { + assert_format("'a'", Code::name); + } + + #[test] + fn test_name_selected() { + assert_format("foo.bar.baz", Code::name); + } + + #[test] + fn test_name_selected_all() { + assert_format("foo.all", Code::name); + } + + #[test] + fn test_name_indexed_single() { + assert_format("foo(0)", Code::name); + } + + #[test] + fn test_name_indexed_multi() { + assert_format("foo(0, 1)", Code::name); + } + + #[test] + fn test_name_slice() { + assert_format("foo(0 to 1)", Code::name); + } + + #[test] + fn test_name_attribute() { + assert_format("prefix'foo", Code::name); + } + + #[test] + fn test_name_attribute_expression() { + assert_format("prefix'foo(expr + 1)", Code::name); + } + + #[test] + fn test_name_attribute_signature() { + assert_format("prefix[return natural]'foo", Code::name); + } + + #[test] + fn test_name_attribute_signature_expression() { + assert_format("prefix[return natural]'foo(expr + 1)", Code::name); + } + + #[test] + fn test_name_function_call_no_args() { + assert_format("foo", |code| { + Name::CallOrIndexed(Box::new(code.function_call().item)) + }); + } + + #[test] + fn test_name_function_call_no_formal() { + assert_format("foo(0)", Code::name); + } + + #[test] + fn test_name_function_call_chained() { + assert_format("prefix(0, 1)(3)", Code::name); + } + + #[test] + fn test_name_function_call_formal() { + assert_format("foo(arg => 0)", Code::name); + } + + #[test] + fn test_name_function_call_actual_part_open() { + assert_format("foo(open, arg => open)", Code::name); + } + + #[test] + fn test_name_external_implicit_relative() { + assert_format("<< signal dut.foo : std_logic >>", Code::name); + } + + #[test] + fn test_name_external_explicit_relative() { + assert_format("<< signal ^.dut.gen(0) : std_logic >>", Code::name); + } + + #[test] + fn test_name_external_explicit_relative_multiple_levels() { + assert_format("<< signal ^.^.^.dut.gen(0) : std_logic >>", Code::name); + } + + #[test] + fn test_name_external_absolute() { + assert_format("<< signal .dut.gen(0) : std_logic >>", Code::name); + } + + #[test] + fn test_name_external_package() { + assert_format("<< signal @lib.pkg : std_logic >>", Code::name); + } + + #[test] + fn test_name_external_object_classes() { + assert_format("<< constant dut.foo : std_logic >>", Code::name); + assert_format("<< signal dut.foo : std_logic >>", Code::name); + assert_format("<< variable dut.foo : std_logic >>", Code::name); + } + + #[test] + fn test_expression_binary() { + assert_format("1 ** 2", Code::expr); + assert_format("1 * 2", Code::expr); + assert_format("1 / 2", Code::expr); + assert_format("1 mod 2", Code::expr); + assert_format("1 rem 2", Code::expr); + assert_format("1 + 2", Code::expr); + assert_format("1 - 2", Code::expr); + assert_format("1 & 2", Code::expr); + assert_format("1 sll 2", Code::expr); + assert_format("1 srl 2", Code::expr); + assert_format("1 sla 2", Code::expr); + assert_format("1 sra 2", Code::expr); + assert_format("1 rol 2", Code::expr); + assert_format("1 ror 2", Code::expr); + assert_format("1 = 2", Code::expr); + assert_format("1 /= 2", Code::expr); + assert_format("1 < 2", Code::expr); + assert_format("1 <= 2", Code::expr); + assert_format("1 > 2", Code::expr); + assert_format("1 >= 2", Code::expr); + assert_format("1 ?= 2", Code::expr); + assert_format("1 ?/= 2", Code::expr); + assert_format("1 ?< 2", Code::expr); + assert_format("1 ?<= 2", Code::expr); + assert_format("1 ?> 2", Code::expr); + assert_format("1 ?>= 2", Code::expr); + assert_format("1 and 2", Code::expr); + assert_format("1 or 2", Code::expr); + assert_format("1 nand 2", Code::expr); + assert_format("1 nor 2", Code::expr); + assert_format("1 xor 2", Code::expr); + assert_format("1 xnor 2", Code::expr); + } + + #[test] + fn test_expression_unary() { + assert_format("?? 1", Code::expr); + assert_format("+1", Code::expr); + assert_format("-1", Code::expr); + assert_format("not 1", Code::expr); + assert_format("abs 1", Code::expr); + assert_format("and 1", Code::expr); + assert_format("or 1", Code::expr); + assert_format("nand 1", Code::expr); + assert_format("nor 1", Code::expr); + assert_format("xor 1", Code::expr); + assert_format("xnor 1", Code::expr); + } + + #[test] + fn test_expression_precedence() { + assert_format("1 * 2 + 3 * 4", Code::expr); + assert_format("(1 + 2) * (3 + 4)", Code::expr); + assert_format("1 + 2 + (3 + 4)", Code::expr); + assert_format("-1 + -2", Code::expr); + assert_format("-(1 + 2)", Code::expr); + // Multiplication has precedence over negation + assert_format("(-1) * -2", Code::expr); + assert_format("-(1 * -2)", Code::expr); + assert_format("-(-1)", Code::expr); + assert_format("-(+1)", Code::expr); + assert_format("-not 1", Code::expr); + assert_format("not -1", Code::expr); + assert_format("not (not 1)", Code::expr); + assert_format("1 - -1", Code::expr); + } + + #[test] + fn test_expression_aggregate_positional() { + assert_format("(1, 2)", Code::expr); + } + + #[test] + fn test_expression_aggregate_named_expression() { + assert_format("(1 => 2)", Code::expr); + } + + #[test] + fn test_expression_aggregate_named_many_choices() { + assert_format("(1 | 2 => 3)", Code::expr); + } + + #[test] + fn test_expression_aggregate_many_named_others() { + assert_format("(1 | 2 => 3, others => 4)", Code::expr); + } + + #[test] + fn test_expression_aggregate_named_range() { + assert_format("(0 to 1 => 2)", Code::expr); + assert_format("(1 downto 0 => 2)", Code::expr); + } + + #[test] + fn test_expression_qualified() { + assert_format("foo'(1 + 2)", Code::expr); + } + + #[test] + fn test_expression_name() { + assert_format("foo.bar.baz", Code::expr); + } + + #[test] + fn test_expression_literal_string() { + assert_format("\"string\"", Code::expr); + } + + #[test] + fn test_expression_literal_bit_string() { + assert_format("b\"0110\"", Code::expr); + assert_format("o\"1377\"", Code::expr); + assert_format("x\"Aa5F\"", Code::expr); + assert_format("ub\"0110\"", Code::expr); + assert_format("uo\"1377\"", Code::expr); + assert_format("ux\"Aa5F\"", Code::expr); + assert_format("sb\"0110\"", Code::expr); + assert_format("so\"1377\"", Code::expr); + assert_format("sx\"Aa5F\"", Code::expr); + assert_format("d\"1234\"", Code::expr); + } + + #[test] + fn test_expression_literal_bit_string_with_length() { + assert_format("3b\"0110\"", Code::expr); + assert_format("10o\"1377\"", Code::expr); + assert_format("15x\"5FaA\"", Code::expr); + assert_format("3ub\"0110\"", Code::expr); + assert_format("10uo\"1377\"", Code::expr); + assert_format("15ux\"5FaA\"", Code::expr); + assert_format("3sb\"0110\"", Code::expr); + assert_format("10so\"1377\"", Code::expr); + assert_format("15sx\"5FaA\"", Code::expr); + assert_format("12d\"1234\"", Code::expr); + } + + #[test] + fn test_expression_literal_character() { + assert_format("'a'", Code::expr); + } + + #[test] + fn test_expression_literal_integer() { + assert_format("123", Code::expr); + } + + #[test] + fn test_expression_literal_real() { + assert_format("12.3", Code::expr); + } + + #[test] + fn test_expression_literal_physical_integer() { + assert_format("1 ns", Code::expr); + } + + #[test] + fn test_expression_literal_physical_real() { + assert_format("1.1 ns", Code::expr); + } + + #[test] + fn parses_null_literal() { + assert_format("null", Code::expr); + } + + #[test] + fn test_expression_new_allocator_qualified() { + assert_format("new integer_vector'(0, 1)", Code::expr); + } + + #[test] + fn test_expression_new_allocator_subtype() { + assert_format("new integer_vector", Code::expr); + } + + #[test] + fn test_expression_new_allocator_subtype_constraint() { + assert_format("new integer_vector(0 to 1)", Code::expr); + } + + #[test] + fn test_expression_new_allocator_subtype_constraint_range_attribute() { + assert_format("new integer_vector(foo'range)", Code::expr); + } + + #[test] + fn test_discrete_range() { + assert_format("foo.bar", Code::discrete_range); + } + + #[test] + fn test_discrete_range_range() { + assert_format("foo.bar range 1 to 4", Code::discrete_range); + } + + #[test] + fn test_discrete_range_range_attribute() { + assert_format("foo.bar'range", Code::discrete_range); + } + + #[test] + fn test_subtype_indication_without_constraint() { + assert_format("std_logic", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_resolution_function() { + assert_format("resolve std_logic", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_array_element_resolution_function() { + assert_format("(resolve) integer_vector", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_record_element_resolution_function() { + assert_format("(elem resolve) rec_t", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_record_element_resolution_function_many() { + assert_format( + "(elem1 (resolve1), elem2 resolve2, elem3 (sub_elem sub_resolve)) rec_t", + Code::subtype_indication, + ); + } + + #[test] + fn test_subtype_indication_with_resolution_function_selected_name() { + assert_format("lib.foo.resolve std_logic", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_range() { + assert_format("integer range 0 to 2 - 1", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_range_attribute() { + assert_format("integer range lib.foo.bar'range", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_array_constraint_range() { + assert_format("integer_vector(2 - 1 downto 0)", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_array_constraint_discrete() { + assert_format("integer_vector(lib.foo.bar)", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_array_constraint_attribute() { + assert_format( + "integer_vector(lib.pkg.bar'range)", + Code::subtype_indication, + ); + } + + #[test] + fn test_subtype_indication_with_array_constraint_open() { + assert_format("integer_vector(open)", Code::subtype_indication); + } + + #[test] + fn test_subtype_indication_with_multi_dim_array_constraints() { + assert_format( + "integer_vector(2 - 1 downto 0, 11 to 14)", + Code::subtype_indication, + ); + } + + #[test] + fn test_subtype_indication_with_array_element_constraint() { + assert_format( + "integer_vector(2 - 1 downto 0, 11 to 14)(foo to bar)", + Code::subtype_indication, + ); + } + + #[test] + fn test_subtype_indication_with_record_constraint() { + assert_format( + "axi_m2s_t(tdata(2 - 1 downto 0), tuser(3 to 5))", + Code::subtype_indication, + ); + } + + #[test] + fn test_type_declaration_integer() { + assert_format("type foo is range 0 to 1;", Code::type_decl); + } + + #[test] + fn test_type_declaration_enumeration() { + assert_format("type foo is (alpha, beta);", Code::type_decl); + } + + #[test] + fn test_type_declaration_enumeration_character() { + assert_format("type foo is ('a', 'b');", Code::type_decl); + } + + #[test] + fn test_type_declaration_enumeration_mixed() { + assert_format("type foo is (ident, 'b');", Code::type_decl); + } + + #[test] + fn test_type_declaration_array_with_index_subtype() { + assert_format( + "type foo is array (natural range <>) of boolean;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_array_with_discrete_subtype() { + assert_format("type foo is array (natural) of boolean;", Code::type_decl); + } + + #[test] + fn test_type_declaration_array_with_selected_name() { + assert_format( + "type foo is array (lib.pkg.foo) of boolean;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_array_with_range_attribute() { + assert_format( + "type foo is array (arr_t'range) of boolean;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_array_with_constraint() { + assert_format( + "type foo is array (2 - 1 downto 0) of boolean;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_array_mixed() { + assert_format( + "type foo is array (2 - 1 downto 0, integer range <>) of boolean;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_record() { + assert_format( + "type foo is record + element : boolean; +end record;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_record_many() { + assert_format( + "type foo is record + element : boolean; + field : boolean; + other_element : std_logic_vector(0 to 1); +end record;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_subtype() { + assert_format( + "subtype vec_t is integer_vector(2 - 1 downto 0);", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_access() { + assert_format( + "type ptr_t is access integer_vector(2 - 1 downto 0);", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_incomplete() { + assert_format("type incomplete;", Code::type_decl); + } + + #[test] + fn test_type_declaration_file() { + assert_format("type foo is file of character;", Code::type_decl); + } + + #[test] + fn test_type_declaration_protected() { + assert_format_eq( + "type foo is protected +end protected;", + "type foo is protected", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_protected_with_subprograms() { + assert_format_eq( + "type foo is protected + procedure proc; + function fun return ret; +end protected;", + "type foo is protected", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_protected_body() { + assert_format_eq( + "type foo is protected body + variable foo : natural; + procedure proc is + begin + end; +end protected body;", + "type foo is protected body", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_physical() { + assert_format( + "type phys is range 0 to 15 units + primary_unit; +end units;", + Code::type_decl, + ); + } + + #[test] + fn test_type_declaration_physical_secondary_units() { + assert_format( + "type phys is range 0 to 15 units + primary_unit; + secondary_unit = 5 primary_unit; +end units;", + Code::type_decl, + ); + } + + #[test] + fn test_object_declaration_constant() { + assert_format("constant foo : natural;", Code::object_decl); + } + + #[test] + fn test_object_declaration_signal() { + assert_format("signal foo : natural;", Code::object_decl); + } + + #[test] + fn test_object_declaration_variable() { + assert_format("variable foo : natural;", Code::object_decl); + } + + #[test] + fn test_object_declaration_shared_variable() { + assert_format("shared variable foo : natural;", Code::object_decl); + } + + #[test] + fn test_object_declaration_optional_expression() { + assert_format("constant foo : natural := 0;", Code::object_decl); + } + + #[test] + fn test_file_declaration() { + assert_format("file foo : text;", Code::file_decl); + } + + #[test] + fn test_file_declaration_with_file_name() { + assert_format("file foo : text is \"file_name\";", Code::file_decl); + } + + #[test] + fn test_file_declaration_with_open_information() { + assert_format( + "file foo : text open write_mode is \"file_name\";", + Code::file_decl, + ); + } + + #[test] + fn test_alias_declaration() { + assert_format("alias foo is name;", Code::alias_decl); + } + + #[test] + fn test_alias_declaration_with_subtype_indication() { + assert_format("alias foo : vector(0 to 1) is name;", Code::alias_decl); + } + + #[test] + fn test_alias_declaration_with_signature() { + assert_format("alias foo is name[return natural];", Code::alias_decl); + } + + #[test] + fn test_alias_declaration_with_operator_symbol() { + assert_format("alias \"and\" is name;", Code::alias_decl); + } + + #[test] + fn test_alias_declaration_with_character() { + assert_format("alias 'c' is 'b';", Code::alias_decl); + } + + #[test] + pub fn test_procedure_specification() { + assert_format("procedure foo", Code::subprogram_specification); + } + + #[test] + pub fn test_function_specification() { + assert_format( + "function foo return lib.foo.natural", + Code::subprogram_specification, + ); + } + + #[test] + pub fn test_function_specification_operator() { + assert_format( + "function \"+\" return lib.foo.natural", + Code::subprogram_specification, + ); + } + + #[test] + pub fn test_function_specification_impure() { + assert_format( + "impure function foo return lib.foo.natural", + Code::subprogram_specification, + ); + } + + #[test] + pub fn test_procedure_specification_with_parameters() { + assert_format( + "procedure foo( + constant foo : in natural +)", + Code::subprogram_specification, + ); + } + + #[test] + pub fn test_function_specification_with_parameters() { + assert_format( + "function foo( + constant foo : in natural +) return lib.foo.natural", + Code::subprogram_specification, + ); + } + + #[test] + pub fn test_interface_declaration_object() { + assert_format("signal foo : in std_logic", Code::parameter); + assert_format("signal foo : out std_logic", Code::parameter); + assert_format("signal foo : inout std_logic", Code::parameter); + assert_format("signal foo : buffer std_logic", Code::parameter); + assert_format("signal foo : linkage std_logic", Code::parameter); + + assert_format("constant foo : in std_logic", Code::parameter); + + assert_format("variable foo : in std_logic", Code::parameter); + assert_format("variable foo : out std_logic", Code::parameter); + assert_format("variable foo : inout std_logic", Code::parameter); + assert_format("variable foo : buffer std_logic", Code::parameter); + assert_format("variable foo : linkage std_logic", Code::parameter); + } + + #[test] + fn test_interface_declaration_object_with_expression() { + assert_format("constant foo : in natural := bar(0)", Code::parameter); + } + + #[test] + fn test_interface_declaration_object_generic() { + assert_format("foo : natural := bar(0)", Code::generic); + } + + #[test] + fn test_interface_declaration_object_port() { + assert_format("foo : in natural := bar(0)", Code::port); + } + + #[test] + fn test_interface_declaration_file() { + assert_format("file foo : text", Code::parameter); + } + + #[test] + fn test_interface_declaration_type() { + assert_format("type name", Code::parameter); + } + + #[test] + fn test_interface_declaration_subprogram() { + assert_format("function foo return bar", Code::parameter); + assert_format("procedure foo", Code::parameter); + assert_format("impure function foo return bar", Code::parameter); + } + + #[test] + fn test_interface_declaration_subprogram_default() { + assert_format("function foo return bar is lib.name", Code::parameter); + assert_format("function foo return bar is <>", Code::parameter); + } + + #[test] + fn test_interface_declaration_package_map() { + assert_format( + "package foo is new lib.pkg + generic map ( + foo => bar + )", + Code::parameter, + ); + } + + #[test] + fn test_interface_declaration_package_box() { + assert_format( + "package foo is new lib.pkg + generic map (<>)", + Code::parameter, + ); + } + + #[test] + fn test_interface_declaration_package_default() { + assert_format( + "package foo is new lib.pkg + generic map (default)", + Code::parameter, + ); + } + + #[test] + pub fn test_signature_function_only_return() { + assert_format("[return bar.type_mark]", Code::signature); + } + + #[test] + pub fn test_signature_function_one_argument() { + assert_format("[foo.type_mark return bar.type_mark]", Code::signature); + } + + #[test] + pub fn test_signature_function_many_arguments() { + assert_format( + "[foo.type_mark, foo2.type_mark return bar.type_mark]", + Code::signature, + ); + } + + #[test] + pub fn test_signature_procedure() { + assert_format("[foo.type_mark]", Code::signature); + } + + #[test] + fn test_component_declaration() { + assert_format( + "component foo +end component;", + Code::component_decl, + ); + } + + #[test] + fn test_component_declaration_with_generic() { + assert_format( + "component foo + generic ( + foo : natural + ); +end component;", + Code::component_decl, + ); + } + + #[test] + fn test_component_declaration_with_port() { + assert_format( + "component foo + port ( + foo : inout natural + ); +end component;", + Code::component_decl, + ); + } + + #[test] + fn test_component_declaration_with_multiple() { + assert_format( + "component foo + generic ( + foo : natural; + bar : natural + ); + port ( + baz : in natural; + qux : out std_logic + ); +end component;", + Code::component_decl, + ); + } + + #[test] + fn test_entity_declaration() { + assert_format( + "entity foo is +end entity;", + Code::entity_decl, + ); + } + + #[test] + fn test_entity_declaration_with_generic() { + assert_format( + "entity foo is + generic ( + foo : natural + ); +end entity;", + Code::entity_decl, + ); + } + + #[test] + fn test_entity_declaration_with_port() { + assert_format( + "entity foo is + port ( + foo : inout natural + ); +end entity;", + Code::entity_decl, + ); + } + + #[test] + fn test_entity_declaration_with_multiple() { + assert_format( + "entity foo is + generic ( + foo : natural; + bar : natural + ); + port ( + baz : in natural; + qux : out std_logic + ); +end entity;", + Code::entity_decl, + ); + } + + #[test] + fn test_for_generate_statement() { + assert_format_eq( + "for idx in 0 to 1 generate +end generate;", + "for idx in 0 to 1 generate", + |code| { + assert_matches!( + code.concurrent_statement().statement.item, + ConcurrentStatement::ForGenerate(gen) => gen + ) + }, + ); + } + + #[test] + fn test_context_declaration() { + assert_format_eq( + "context ident is +end context;", + "context ident", + |code| { + assert_matches!( + code.design_file().design_units.remove(0), + (_, AnyDesignUnit::Primary(AnyPrimaryUnit::Context(context))) => context + ) + }, + ); + } + + #[test] + fn test_package_instantiation() { + assert_format("package ident is new lib.foo.bar;", |code| { + assert_matches!( + code.design_file().design_units.remove(0), + (_, AnyDesignUnit::Primary(AnyPrimaryUnit::PackageInstance(instance))) => instance + ) + }); + } + + #[test] + fn test_package_instantiation_generic_map() { + assert_format( + "package ident is new lib.foo.bar + generic map ( + foo => bar, + baz => qux + );", + |code| { + assert_matches!( + code.design_file().design_units.remove(0), + (_, AnyDesignUnit::Primary(AnyPrimaryUnit::PackageInstance(instance))) => instance + ) + }, + ); + } + + #[test] + fn test_configuration_declaration() { + assert_format_eq( + "configuration cfg of entity_name is + for rtl(0) + end for; +end;", + "configuration cfg of entity_name", + |code| { + assert_matches!( + code.design_file().design_units.remove(0), + (_, AnyDesignUnit::Primary(AnyPrimaryUnit::Configuration(unit))) => unit + ) + }, + ); + } + + #[test] + fn test_package_declaration() { + assert_format_eq( + "package pkg_name is +end package;", + "package pkg_name", + |code| { + assert_matches!( + code.design_file().design_units.remove(0), + (_, AnyDesignUnit::Primary(AnyPrimaryUnit::Package(unit))) => unit + ) + }, + ); + } + + #[test] + fn test_package_declaration_with_generic() { + assert_format_eq( + "package pkg_name is + generic ( + type foo; + type bar + ); +end package;", + "package pkg_name is + generic ( + type foo; + type bar + );", + |code| { + assert_matches!( + code.design_file().design_units.remove(0), + (_, AnyDesignUnit::Primary(AnyPrimaryUnit::Package(unit))) => unit + ) + }, + ); + } + + #[test] + fn write_subprogram_instantiation() { + assert_format_eq( + "function my_func is new func;", + "function my_func is new func", + Code::subprogram_instantiation, + ); + } + + #[test] + fn write_subprogram_instantiation_signature() { + assert_format_eq( + "function my_func is new func [bit return bit_vector];", + "function my_func is new func [bit return bit_vector]", + Code::subprogram_instantiation, + ); + } + + #[test] + fn write_subprogram_instantiation_signature_generic_map() { + assert_format_eq( + "procedure proc is new proc generic map (x => x, y => a.b);", + "procedure proc is new proc generic map ( + x => x, + y => a.b +)", + Code::subprogram_instantiation, + ); + } + + #[test] + fn subprogram_instantiation_declaration() { + assert_format_eq( + "procedure proc is new proc generic map (x => x, y => a.b);", + "procedure proc is new proc generic map ( + x => x, + y => a.b +)", + |code| { + assert_matches!( + code.declarative_part().remove(0).item, + Declaration::SubprogramInstantiation(inst) => inst + ) + }, + ); + } +} diff --git a/vhdl_lang/src/ast/search.rs b/vhdl_lang/src/ast/search.rs new file mode 100644 index 0000000..1bc8a26 --- /dev/null +++ b/vhdl_lang/src/ast/search.rs @@ -0,0 +1,2049 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::*; +use crate::analysis::DesignRoot; +use crate::named_entity::{EntRef, HasEntityId, Reference}; +use crate::syntax::{HasTokenSpan, TokenAccess}; + +#[must_use] +#[derive(PartialEq, Debug)] +pub enum SearchResult { + Found, + NotFound, +} + +#[must_use] +pub enum SearchState { + Finished(SearchResult), + NotFinished, +} + +pub use SearchResult::*; +pub use SearchState::*; + +impl SearchState { + fn or_not_found(self) -> SearchResult { + match self { + Finished(result) => result, + NotFinished => NotFound, + } + } +} + +#[derive(PartialEq, Debug)] +pub enum DeclarationItem<'a> { + Object(&'a ObjectDeclaration), + ElementDeclaration(&'a ElementDeclaration), + EnumerationLiteral(&'a Ident, &'a WithDecl>), + InterfaceObject(&'a InterfaceObjectDeclaration), + InterfaceFile(&'a InterfaceFileDeclaration), + File(&'a FileDeclaration), + Type(&'a TypeDeclaration), + InterfaceType(&'a WithDecl), + InterfacePackage(&'a InterfacePackageDeclaration), + PhysicalTypePrimary(&'a WithDecl), + PhysicalTypeSecondary(&'a WithDecl, &'a PhysicalLiteral), + Component(&'a ComponentDeclaration), + Attribute(&'a AttributeDeclaration), + Alias(&'a AliasDeclaration), + SubprogramDecl(&'a SubprogramSpecification), + Subprogram(&'a SubprogramBody), + SubprogramInstantiation(&'a SubprogramInstantiation), + Package(&'a PackageDeclaration), + PackageBody(&'a PackageBody), + PackageInstance(&'a PackageInstantiation), + Configuration(&'a ConfigurationDeclaration), + Entity(&'a EntityDeclaration), + Architecture(&'a ArchitectureBody), + Context(&'a ContextDeclaration), + ForIndex(&'a WithDecl, &'a DiscreteRange), + ForGenerateIndex(Option<&'a Ident>, &'a ForGenerateStatement), + GenerateBody(&'a WithDecl), + ConcurrentStatement(&'a LabeledConcurrentStatement), + SequentialStatement(&'a LabeledSequentialStatement), + View(&'a ModeViewDeclaration), +} + +pub struct FoundDeclaration<'a> { + pub reference: &'a Reference, + pub ast: DeclarationItem<'a>, +} + +impl<'a> FoundDeclaration<'a> { + pub fn new(reference: &'a Reference, ast: DeclarationItem<'a>) -> FoundDeclaration<'a> { + FoundDeclaration { reference, ast } + } +} + +pub trait Searcher { + /// Search an position that has a reference to a declaration + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + _pos: &SrcPos, + _ref: &Reference, + ) -> SearchState { + NotFinished + } + + /// Search a designator that has a reference to a declaration + fn search_designator_ref( + &mut self, + ctx: &dyn TokenAccess, + pos: &SrcPos, + designator: &WithRef, + ) -> SearchState { + self.search_pos_with_ref(ctx, pos, &designator.reference) + } + + /// Search an identifier that has a reference to a declaration + fn search_ident_ref(&mut self, ctx: &dyn TokenAccess, ident: &WithRef) -> SearchState { + self.search_pos_with_ref(ctx, ident.item.pos(ctx), &ident.reference) + } + + /// Search a declaration of a named entity + fn search_decl(&mut self, _ctx: &dyn TokenAccess, _decl: FoundDeclaration<'_>) -> SearchState { + NotFinished + } + + fn search_with_pos(&mut self, _ctx: &dyn TokenAccess, _pos: &SrcPos) -> SearchState { + NotFinished + } +} + +pub trait Search { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult; +} + +#[macro_export] +macro_rules! return_if_found { + ($result:expr) => { + if let Found = $result { + return Found; + }; + }; +} + +#[macro_export] +macro_rules! return_if_finished { + ($result:expr) => { + if let Finished(result) = $result { + return result; + }; + }; +} + +impl Search for Vec { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + for decl in self.iter() { + return_if_found!(decl.search(ctx, searcher)); + } + NotFound + } +} + +impl Search for Option { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + for decl in self.iter() { + return_if_found!(decl.search(ctx, searcher)); + } + NotFound + } +} + +fn search_conditionals( + conditionals: &Conditionals, + item_before_cond: bool, + searcher: &mut impl Searcher, + ctx: &dyn TokenAccess, +) -> SearchResult { + let Conditionals { + conditionals, + else_item, + } = conditionals; + for conditional in conditionals { + let Conditional { condition, item } = conditional; + if item_before_cond { + // If + return_if_found!(item.search(ctx, searcher)); + return_if_found!(condition.search(ctx, searcher)); + } else { + // When + return_if_found!(condition.search(ctx, searcher)); + return_if_found!(item.search(ctx, searcher)); + } + } + if let Some((expr, _)) = else_item { + return_if_found!(expr.search(ctx, searcher)); + } + NotFound +} + +fn search_alternatives( + alternatives: &[Alternative], + item_before_choice: bool, + searcher: &mut impl Searcher, + ctx: &dyn TokenAccess, +) -> SearchResult { + for alternative in alternatives.iter() { + let Alternative { + choices, + item, + span: _, + } = &alternative; + if item_before_choice { + return_if_found!(item.search(ctx, searcher)); + return_if_found!(choices.search(ctx, searcher)); + } else { + return_if_found!(choices.search(ctx, searcher)); + return_if_found!(item.search(ctx, searcher)); + } + } + NotFound +} + +fn search_selection( + selection: &Selection, + item_before_cond: bool, + searcher: &mut impl Searcher, + ctx: &dyn TokenAccess, +) -> SearchResult { + let Selection { + expression, + alternatives, + } = selection; + return_if_found!(expression.search(ctx, searcher)); + return_if_found!(search_alternatives( + alternatives, + item_before_cond, + searcher, + ctx, + )); + + NotFound +} + +fn search_assignment( + target: &WithTokenSpan, + rhs: &AssignmentRightHand, + searcher: &mut impl Searcher, + ctx: &dyn TokenAccess, +) -> SearchResult { + match rhs { + AssignmentRightHand::Simple(item) => { + return_if_found!(target.search(ctx, searcher)); + item.search(ctx, searcher) + } + AssignmentRightHand::Conditional(conditionals) => { + return_if_found!(target.search(ctx, searcher)); + search_conditionals(conditionals, true, searcher, ctx) + } + AssignmentRightHand::Selected(selection) => { + let Selection { + expression, + alternatives, + } = selection; + // expression comes before target + return_if_found!(expression.search(ctx, searcher)); + return_if_found!(target.search(ctx, searcher)); + search_alternatives(alternatives, true, searcher, ctx) + } + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, &self.pos(ctx))); + + match self.item { + Choice::DiscreteRange(ref drange) => { + return_if_found!(drange.search(ctx, searcher)); + } + Choice::Expression(ref expr) => { + return_if_found!(search_pos_expr(ctx, &self.pos(ctx), expr, searcher)); + } + Choice::Others => {} + } + NotFound + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + self.item.search(ctx, searcher) + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self.item { + Target::Name(ref name) => search_pos_name(&self.pos(ctx), name, searcher, ctx), + Target::Aggregate(ref assocs) => assocs.search(ctx, searcher), + } + } +} + +impl Search for SeparatedList { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + for item in self.items.iter() { + return_if_found!(item.search(ctx, searcher)); + } + NotFound + } +} + +impl Search for LabeledSequentialStatement { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.label.decl, DeclarationItem::SequentialStatement(self)) + ) + .or_not_found()); + match self.statement.item { + SequentialStatement::Return(ref ret) => { + let ReturnStatement { ref expression } = ret; + return_if_found!(expression.search(ctx, searcher)); + } + SequentialStatement::ProcedureCall(ref pcall) => { + return_if_finished!(searcher.search_with_pos(ctx, &pcall.pos(ctx))); + return_if_found!(pcall.item.search(ctx, searcher)); + } + SequentialStatement::If(ref ifstmt) => { + return_if_found!(search_conditionals(&ifstmt.conds, false, searcher, ctx)); + } + SequentialStatement::Wait(ref wait_stmt) => { + let WaitStatement { + sensitivity_clause, + condition_clause, + timeout_clause, + } = wait_stmt; + return_if_found!(sensitivity_clause.search(ctx, searcher)); + return_if_found!(condition_clause.search(ctx, searcher)); + return_if_found!(timeout_clause.search(ctx, searcher)); + } + SequentialStatement::Assert(ref assert_stmt) => { + let AssertStatement { + condition, + report, + severity, + } = assert_stmt; + return_if_found!(condition.search(ctx, searcher)); + return_if_found!(report.search(ctx, searcher)); + return_if_found!(severity.search(ctx, searcher)); + } + SequentialStatement::Report(ref report_stmt) => { + let ReportStatement { report, severity } = report_stmt; + return_if_found!(report.search(ctx, searcher)); + return_if_found!(severity.search(ctx, searcher)); + } + SequentialStatement::Exit(ref exit_stmt) => { + let ExitStatement { + condition, + loop_label, + } = exit_stmt; + if let Some(loop_label) = loop_label { + return_if_found!(searcher + .search_pos_with_ref(ctx, loop_label.item.pos(ctx), &loop_label.reference) + .or_not_found()); + } + return_if_found!(condition.search(ctx, searcher)); + } + SequentialStatement::Next(ref next_stmt) => { + let NextStatement { + condition, + loop_label, + } = next_stmt; + if let Some(loop_label) = loop_label { + return_if_found!(searcher + .search_pos_with_ref(ctx, loop_label.item.pos(ctx), &loop_label.reference) + .or_not_found()); + } + return_if_found!(condition.search(ctx, searcher)); + } + SequentialStatement::Case(ref case_stmt) => { + return_if_found!(case_stmt.search(ctx, searcher)); + } + SequentialStatement::Loop(ref loop_stmt) => { + let LoopStatement { + iteration_scheme, + statements, + .. + } = loop_stmt; + match iteration_scheme { + Some(IterationScheme::For(ref index, ref drange)) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &index.decl, + DeclarationItem::ForIndex(index, drange) + ) + ) + .or_not_found()); + return_if_found!(drange.search(ctx, searcher)); + return_if_found!(statements.search(ctx, searcher)); + } + Some(IterationScheme::While(ref expr)) => { + return_if_found!(expr.search(ctx, searcher)); + return_if_found!(statements.search(ctx, searcher)); + } + None => { + return_if_found!(statements.search(ctx, searcher)); + } + } + } + SequentialStatement::SignalAssignment(ref assign) => { + // @TODO more + let SignalAssignment { target, rhs, .. } = assign; + return_if_found!(search_assignment(target, rhs, searcher, ctx)); + } + SequentialStatement::VariableAssignment(ref assign) => { + let VariableAssignment { target, rhs } = assign; + return_if_found!(search_assignment(target, rhs, searcher, ctx)); + } + SequentialStatement::SignalForceAssignment(ref assign) => { + let SignalForceAssignment { + target, + force_mode: _, + rhs, + } = assign; + return_if_found!(search_assignment(target, rhs, searcher, ctx)); + } + SequentialStatement::SignalReleaseAssignment(ref assign) => { + let SignalReleaseAssignment { + target, + force_mode: _, + span: _, + } = assign; + return_if_found!(target.search(ctx, searcher)); + } + SequentialStatement::Null => {} + } + + if let Some(end_label_pos) = self.statement.item.end_label_pos() { + return_if_found!(searcher + .search_pos_with_ref(ctx, end_label_pos, &self.label.decl) + .or_not_found()); + } + + NotFound + } +} + +impl Search for GenerateBody { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + let GenerateBody { + alternative_label, + decl, + statements, + end_label: end_label_pos, + .. + } = self; + if let Some(ref label) = alternative_label { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&label.decl, DeclarationItem::GenerateBody(label)) + ) + .or_not_found()); + } + if let Some((decl, _)) = decl { + return_if_found!(decl.search(ctx, searcher)); + } + return_if_found!(statements.search(ctx, searcher)); + + if let Some(ref label) = alternative_label { + if let Some(end_label_pos) = end_label_pos { + return_if_found!(searcher + .search_pos_with_ref(ctx, ctx.get_pos(*end_label_pos), &label.decl) + .or_not_found()); + } + } + + NotFound + } +} + +impl Search for InstantiationStatement { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self.unit { + InstantiatedUnit::Entity(ref ent_name, ref architecture_name) => { + return_if_found!(ent_name.search(ctx, searcher)); + if let Some(ref architecture_name) = architecture_name { + return_if_found!(searcher + .search_pos_with_ref( + ctx, + architecture_name.item.pos(ctx), + &architecture_name.reference + ) + .or_not_found()); + } + } + InstantiatedUnit::Component(ref component_name) => { + return_if_found!(component_name.search(ctx, searcher)); + } + InstantiatedUnit::Configuration(ref config_name) => { + return_if_found!(config_name.search(ctx, searcher)); + } + }; + return_if_found!(self.generic_map.search(ctx, searcher)); + return_if_found!(self.port_map.search(ctx, searcher)); + + NotFound + } +} + +impl Search for SensitivityList { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + SensitivityList::Names(names) => names.search(ctx, searcher), + SensitivityList::All => NotFound, + } + } +} + +impl Search for LabeledConcurrentStatement { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.label.decl, DeclarationItem::ConcurrentStatement(self)) + ) + .or_not_found()); + match self.statement.item { + ConcurrentStatement::Block(ref block) => { + // @TODO guard condition + return_if_found!(block.decl.search(ctx, searcher)); + return_if_found!(block.statements.search(ctx, searcher)); + } + ConcurrentStatement::Process(ref process) => { + let ProcessStatement { + postponed: _, + sensitivity_list, + decl, + statements, + end_label_pos: _, + .. + } = process; + if let Some(sensitivity_list) = sensitivity_list { + return_if_found!(sensitivity_list.item.search(ctx, searcher)); + } + return_if_found!(decl.search(ctx, searcher)); + return_if_found!(statements.search(ctx, searcher)); + } + ConcurrentStatement::ForGenerate(ref gen) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &gen.index_name.decl, + DeclarationItem::ForGenerateIndex(self.label.tree.as_ref(), gen) + ) + ) + .or_not_found()); + let ForGenerateStatement { + index_name: _, + discrete_range, + body, + end_label_pos: _, + .. + } = gen; + return_if_found!(discrete_range.search(ctx, searcher)); + return_if_found!(body.search(ctx, searcher)); + } + ConcurrentStatement::IfGenerate(ref gen) => { + return_if_found!(search_conditionals(&gen.conds, false, searcher, ctx)); + } + ConcurrentStatement::CaseGenerate(ref gen) => { + return_if_found!(search_selection(&gen.sels, false, searcher, ctx)); + } + ConcurrentStatement::Instance(ref inst) => { + return_if_found!(inst.search(ctx, searcher)); + } + ConcurrentStatement::Assignment(ref assign) => { + let ConcurrentSignalAssignment { assignment, .. } = assign; + return_if_found!(search_assignment( + &assignment.target, + &assignment.rhs, + searcher, + ctx + )); + } + ConcurrentStatement::ProcedureCall(ref pcall) => { + let ConcurrentProcedureCall { + postponed: _postponed, + call, + .. + } = pcall; + return_if_finished!(searcher.search_with_pos(ctx, &call.pos(ctx))); + return_if_found!(call.item.search(ctx, searcher)); + } + ConcurrentStatement::Assert(ref assert) => { + let ConcurrentAssertStatement { + postponed: _postponed, + statement: + AssertStatement { + condition, + report, + severity, + }, + } = assert; + return_if_found!(condition.search(ctx, searcher)); + return_if_found!(report.search(ctx, searcher)); + return_if_found!(severity.search(ctx, searcher)); + } + }; + + if let Some(end_label_pos) = self.statement.item.end_label_pos() { + return_if_found!(searcher + .search_pos_with_ref(ctx, end_label_pos, &self.label.decl) + .or_not_found()); + } + + NotFound + } +} + +impl Search for WithToken> { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, self.pos(ctx))); + searcher + .search_designator_ref(ctx, self.pos(ctx), &self.item) + .or_not_found() + } +} + +fn search_pos_name( + pos: &SrcPos, + name: &Name, + searcher: &mut impl Searcher, + ctx: &dyn TokenAccess, +) -> SearchResult { + match name { + Name::Selected(ref prefix, ref designator) => { + return_if_found!(prefix.search(ctx, searcher)); + return_if_found!(designator.search(ctx, searcher)); + NotFound + } + Name::SelectedAll(ref prefix) => { + return_if_found!(prefix.search(ctx, searcher)); + NotFound + } + Name::Designator(ref designator) => searcher + .search_designator_ref(ctx, pos, designator) + .or_not_found(), + Name::Slice(ref prefix, ref dranges) => { + return_if_found!(prefix.search(ctx, searcher)); + return_if_found!(dranges.search(ctx, searcher)); + NotFound + } + Name::CallOrIndexed(ref fcall) => fcall.search(ctx, searcher), + Name::Attribute(ref attr) => { + // @TODO more + let AttributeName { + name, expr, attr, .. + } = attr.as_ref(); + return_if_found!(name.search(ctx, searcher)); + if let AttributeDesignator::Ident(ref user_attr) = attr.item { + return_if_finished!(searcher.search_pos_with_ref( + ctx, + attr.pos(ctx), + &user_attr.reference + )); + } + if let Some(expr) = expr { + return_if_found!(expr.search(ctx, searcher)); + } + NotFound + } + Name::External(ref ename) => { + let ExternalName { subtype, .. } = ename.as_ref(); + return_if_found!(subtype.search(ctx, searcher)); + NotFound + } + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, &self.pos(ctx))); + search_pos_name(&self.pos(ctx), &self.item, searcher, ctx) + } +} + +impl Search for ElementConstraint { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + // @TODO more + let ElementConstraint { constraint, .. } = self; + constraint.search(ctx, searcher) + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, &self.pos(ctx))); + self.item.search(ctx, searcher) + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + self.item.search(ctx, searcher) + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, &self.pos(ctx))); + match self.item { + SubtypeConstraint::Array(ref dranges, ref constraint) => { + for drange in dranges { + return_if_found!(&drange.item.search(ctx, searcher)); + } + if let Some(ref constraint) = constraint { + return_if_found!(constraint.search(ctx, searcher)); + } + } + SubtypeConstraint::Range(ref range) => { + return_if_found!(range.search(ctx, searcher)); + } + SubtypeConstraint::Record(ref constraints) => { + return_if_found!(constraints.search(ctx, searcher)); + } + } + NotFound + } +} + +impl Search for SubtypeIndication { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + // @TODO more + let SubtypeIndication { + type_mark, + constraint, + .. + } = self; + return_if_found!(type_mark.search(ctx, searcher)); + return_if_found!(constraint.search(ctx, searcher)); + NotFound + } +} + +impl Search for RangeConstraint { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + let RangeConstraint { + direction: _, + left_expr, + right_expr, + } = self; + return_if_found!(left_expr.search(ctx, searcher)); + return_if_found!(right_expr.search(ctx, searcher)); + NotFound + } +} + +impl Search for AttributeName { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + // @TODO more + let AttributeName { name, .. } = self; + name.search(ctx, searcher) + } +} + +impl Search for Range { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + Range::Range(constraint) => { + return_if_found!(constraint.search(ctx, searcher)); + } + Range::Attribute(attr) => { + return_if_found!(attr.search(ctx, searcher)); + } + } + NotFound + } +} + +impl Search for DiscreteRange { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + DiscreteRange::Discrete(ref type_mark, ref constraint) => { + return_if_found!(type_mark.search(ctx, searcher)); + constraint.search(ctx, searcher) + } + DiscreteRange::Range(ref constraint) => constraint.search(ctx, searcher), + } + } +} + +impl Search for TypeDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::Type(self)) + ) + .or_not_found()); + + match &self.def { + TypeDefinition::ProtectedBody(ref body) => { + return_if_found!(body.decl.search(ctx, searcher)); + } + TypeDefinition::Protected(ref prot_decl) => { + for item in prot_decl.items.iter() { + match item { + ProtectedTypeDeclarativeItem::Subprogram(ref subprogram) => { + return_if_found!(subprogram.search(ctx, searcher)); + } + } + } + } + TypeDefinition::Record(ref element_decls) => { + for elem in element_decls { + for ident in &elem.idents { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &ident.decl, + DeclarationItem::ElementDeclaration(elem) + ) + ) + .or_not_found()); + } + return_if_found!(elem.subtype.search(ctx, searcher)); + } + } + TypeDefinition::Access(ref subtype_indication) => { + return_if_found!(subtype_indication.search(ctx, searcher)); + } + TypeDefinition::Array(ref indexes, _, ref subtype_indication) => { + for index in indexes.iter() { + match index { + ArrayIndex::IndexSubtypeDefintion(ref type_mark) => { + return_if_found!(type_mark.search(ctx, searcher)); + } + ArrayIndex::Discrete(ref drange) => { + return_if_found!(drange.item.search(ctx, searcher)); + } + } + } + return_if_found!(subtype_indication.search(ctx, searcher)); + } + TypeDefinition::Subtype(ref subtype_indication) => { + return_if_found!(subtype_indication.search(ctx, searcher)); + } + TypeDefinition::Numeric(ref range) => { + return_if_found!(range.search(ctx, searcher)); + } + TypeDefinition::File(ref type_mark) => { + return_if_found!(type_mark.search(ctx, searcher)); + } + TypeDefinition::Incomplete(ref reference) => { + // Incomplete type should reference full declaration + return_if_found!(searcher + .search_pos_with_ref(ctx, self.ident.pos(ctx), reference) + .or_not_found()); + } + TypeDefinition::Enumeration(ref literals) => { + for literal in literals { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &literal.decl, + DeclarationItem::EnumerationLiteral(&self.ident.tree, literal) + ) + ) + .or_not_found()); + } + } + TypeDefinition::Physical(ref physical) => { + let PhysicalTypeDeclaration { + range, + units_token: _, + primary_unit, + secondary_units, + } = physical; + return_if_found!(range.search(ctx, searcher)); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &primary_unit.decl, + DeclarationItem::PhysicalTypePrimary(primary_unit) + ) + ) + .or_not_found()); + for (ident, literal) in secondary_units.iter() { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &ident.decl, + DeclarationItem::PhysicalTypeSecondary(ident, &literal.item) + ) + ) + .or_not_found()); + return_if_found!(searcher + .search_ident_ref(ctx, &literal.item.unit) + .or_not_found()); + } + } + } + NotFound + } +} + +fn search_pos_expr( + ctx: &dyn TokenAccess, + pos: &SrcPos, + expr: &Expression, + searcher: &mut impl Searcher, +) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, pos)); + match expr { + Expression::Binary(ref op, ref left, ref right) => { + return_if_found!(searcher + .search_pos_with_ref(ctx, op.pos(ctx), &op.item.reference) + .or_not_found()); + return_if_found!(left.search(ctx, searcher)); + right.search(ctx, searcher) + } + Expression::Unary(ref op, ref expr) => { + return_if_found!(searcher + .search_pos_with_ref(ctx, op.pos(ctx), &op.item.reference) + .or_not_found()); + expr.search(ctx, searcher) + } + Expression::Name(ref name) => search_pos_name(pos, name, searcher, ctx), + Expression::Aggregate(ref assocs) => assocs.search(ctx, searcher), + Expression::Qualified(ref qexpr) => qexpr.search(ctx, searcher), + Expression::New(ref alloc) => { + return_if_finished!(searcher.search_with_pos(ctx, &alloc.pos(ctx))); + match alloc.item { + Allocator::Qualified(ref qexpr) => qexpr.search(ctx, searcher), + Allocator::Subtype(ref subtype) => subtype.search(ctx, searcher), + } + } + Expression::Literal(literal) => match literal { + Literal::Physical(PhysicalLiteral { unit, .. }) => { + searcher.search_ident_ref(ctx, unit).or_not_found() + } + _ => NotFound, + }, + Expression::Parenthesized(expr) => { + search_pos_expr(ctx, &expr.span.pos(ctx), &expr.item, searcher) + } + } +} + +impl Search for ElementAssociation { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + ElementAssociation::Named(ref choices, ref expr) => { + return_if_found!(choices.search(ctx, searcher)); + return_if_found!(expr.search(ctx, searcher)); + } + ElementAssociation::Positional(ref expr) => { + return_if_found!(expr.search(ctx, searcher)); + } + } + NotFound + } +} + +impl Search for QualifiedExpression { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + let QualifiedExpression { type_mark, expr } = self; + return_if_found!(type_mark.search(ctx, searcher)); + return_if_found!(expr.search(ctx, searcher)); + NotFound + } +} + +impl Search for AssociationElement { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + let AssociationElement { formal, actual } = self; + if let Some(formal) = formal { + return_if_found!(search_pos_name( + &formal.pos(ctx), + &formal.item, + searcher, + ctx + )); + } + + match actual.item { + ActualPart::Expression(ref expr) => { + return_if_found!(search_pos_expr(ctx, &actual.pos(ctx), expr, searcher)); + } + ActualPart::Open => {} + } + NotFound + } +} + +impl Search for CallOrIndexed { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + let CallOrIndexed { name, parameters } = self; + return_if_found!(name.search(ctx, searcher)); + return_if_found!(parameters.search(ctx, searcher)); + NotFound + } +} + +impl Search for Waveform { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + Waveform::Elements(ref elems) => { + for elem in elems.iter() { + let WaveformElement { value, after } = elem; + return_if_found!(value.search(ctx, searcher)); + return_if_found!(after.search(ctx, searcher)); + } + } + Waveform::Unaffected(_) => {} + } + NotFound + } +} + +impl Search for WithTokenSpan { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + search_pos_expr(ctx, &self.span.pos(ctx), &self.item, searcher) + } +} + +impl Search for ObjectDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + for ident in &self.idents { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&ident.decl, DeclarationItem::Object(self)) + ) + .or_not_found()); + } + return_if_found!(self.subtype_indication.search(ctx, searcher)); + if let Some(ref expr) = self.expression { + expr.search(ctx, searcher) + } else { + NotFound + } + } +} + +impl Search for Signature { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + Signature::Function(args, ret) => { + return_if_found!(args.search(ctx, searcher)); + return_if_found!(ret.search(ctx, searcher)); + } + Signature::Procedure(args) => { + return_if_found!(args.search(ctx, searcher)); + } + } + NotFound + } +} + +impl Search for InterfaceList { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + self.items.search(ctx, searcher) + } +} + +impl Search for Declaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + Declaration::Object(object) => { + return_if_found!(object.search(ctx, searcher)); + } + Declaration::Type(typ) => { + return_if_found!(typ.search(ctx, searcher)); + } + Declaration::SubprogramBody(body) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + body.specification.ent_id_ref(), + DeclarationItem::Subprogram(body) + ) + ) + .or_not_found()); + return_if_found!(search_subpgm_inner(&body.specification, ctx, searcher)); + return_if_found!(body.declarations.search(ctx, searcher)); + return_if_found!(body.statements.search(ctx, searcher)); + } + Declaration::SubprogramDeclaration(decl) => { + return_if_found!(decl.search(ctx, searcher)); + } + Declaration::SubprogramInstantiation(decl) => { + return_if_found!(decl.search(ctx, searcher)); + } + Declaration::Attribute(Attribute::Declaration(decl)) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&decl.ident.decl, DeclarationItem::Attribute(decl)) + ) + .or_not_found()); + return_if_found!(decl.type_mark.search(ctx, searcher)); + } + Declaration::Attribute(Attribute::Specification(AttributeSpecification { + ident, + entity_name, + expr, + .. + })) => { + return_if_found!(searcher.search_ident_ref(ctx, ident).or_not_found()); + if let EntityName::Name(EntityTag { + designator, + signature, + }) = entity_name + { + return_if_found!(searcher + .search_pos_with_ref(ctx, designator.pos(ctx), &designator.item.reference) + .or_not_found()); + if let Some(signature) = signature { + return_if_found!(signature.item.search(ctx, searcher)); + } + } + + return_if_found!(expr.search(ctx, searcher)); + } + Declaration::Alias(alias) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &alias.designator.decl, + DeclarationItem::Alias(alias) + ) + ) + .or_not_found()); + let AliasDeclaration { + subtype_indication, + name, + signature, + .. + } = alias; + return_if_found!(subtype_indication.search(ctx, searcher)); + return_if_found!(name.search(ctx, searcher)); + if let Some(signature) = signature { + return_if_found!(signature.item.search(ctx, searcher)); + } + } + Declaration::Use(use_clause) => { + return_if_found!(searcher + .search_with_pos(ctx, &use_clause.get_pos(ctx)) + .or_not_found()); + return_if_found!(use_clause.name_list.search(ctx, searcher)); + } + Declaration::Component(component) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &component.ident.decl, + DeclarationItem::Component(component) + ) + ) + .or_not_found()); + let ComponentDeclaration { + generic_list, + port_list, + .. + } = component; + if let Some(generic_list) = generic_list { + return_if_found!(generic_list.search(ctx, searcher)); + } + if let Some(port_list) = port_list { + return_if_found!(port_list.search(ctx, searcher)); + } + } + + Declaration::File(file) => { + for ident in &file.idents { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&ident.decl, DeclarationItem::File(file)) + ) + .or_not_found()); + } + let FileDeclaration { + idents: _, + colon_token: _, + subtype_indication, + open_info, + file_name, + } = file; + return_if_found!(subtype_indication.search(ctx, searcher)); + if let Some((_, open_info)) = open_info { + return_if_found!(open_info.search(ctx, searcher)); + } + if let Some((_, file_name)) = file_name { + return_if_found!(file_name.search(ctx, searcher)); + } + } + + Declaration::Package(ref package_instance) => { + return_if_found!(package_instance.search(ctx, searcher)); + } + + Declaration::Configuration(_) => { + // @TODO + } + Declaration::View(view) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&view.ident.decl, DeclarationItem::View(view)) + ) + .or_not_found()); + let ModeViewDeclaration { typ, elements, .. } = view; + return_if_found!(typ.search(ctx, searcher)); + return_if_found!(elements.search(ctx, searcher)); + } + } + NotFound + } +} + +impl Search for ModeViewElement { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + for name in self.names.iter() { + return_if_found!(searcher + .search_pos_with_ref(ctx, name.pos(ctx), &name.decl) + .or_not_found()); + } + NotFound + } +} + +impl Search for ModeIndication { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + ModeIndication::Simple(simple) => simple.search(ctx, searcher), + ModeIndication::View(view) => view.search(ctx, searcher), + } + } +} + +impl Search for SimpleModeIndication { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.subtype_indication.search(ctx, searcher)); + return_if_found!(self.expression.search(ctx, searcher)); + NotFound + } +} + +impl Search for ModeViewIndication { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.name.search(ctx, searcher)); + if let Some((_, subtype)) = &self.subtype_indication { + return_if_found!(subtype.search(ctx, searcher)); + } + NotFound + } +} + +impl Search for InterfaceDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + match self { + InterfaceDeclaration::Object(ref decl) => { + for ident in &decl.idents { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &ident.decl, + DeclarationItem::InterfaceObject(decl) + ) + ) + .or_not_found()); + } + return_if_found!(decl.mode.search(ctx, searcher)); + } + InterfaceDeclaration::Subprogram(ref subprogram_decl) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + subprogram_decl.specification.ent_id_ref(), + DeclarationItem::SubprogramDecl(&subprogram_decl.specification) + ) + ) + .or_not_found()); + + if let Some(subpgm_default) = &subprogram_decl.default { + match subpgm_default { + SubprogramDefault::Name(selected_name) => { + return_if_found!(selected_name.search(ctx, searcher)); + } + SubprogramDefault::Box => {} + } + } + } + InterfaceDeclaration::Type(decl) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&decl.decl, DeclarationItem::InterfaceType(decl)) + ) + .or_not_found()); + } + InterfaceDeclaration::Package(package_instance) => { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &package_instance.ident.decl, + DeclarationItem::InterfacePackage(package_instance) + ) + ) + .or_not_found()); + return_if_found!(package_instance.package_name.search(ctx, searcher)); + match package_instance.generic_map.item { + InterfacePackageGenericMapAspect::Map(ref generic_map) => { + return_if_found!(generic_map.search(ctx, searcher)); + } + InterfacePackageGenericMapAspect::Box => {} + InterfacePackageGenericMapAspect::Default => {} + } + } + InterfaceDeclaration::File(decl) => { + for ident in &decl.idents { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &ident.decl, + DeclarationItem::InterfaceFile(decl) + ) + ) + .or_not_found()); + } + } + }; + NotFound + } +} + +impl Search for SubprogramDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + self.specification.search(ctx, searcher) + } +} + +impl Search for SubprogramSpecification { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(self.ent_id_ref(), DeclarationItem::SubprogramDecl(self)) + ) + .or_not_found()); + search_subpgm_inner(self, ctx, searcher) + } +} + +fn search_subpgm_inner( + subgpm: &SubprogramSpecification, + ctx: &dyn TokenAccess, + searcher: &mut impl Searcher, +) -> SearchResult { + match subgpm { + SubprogramSpecification::Function(ref decl) => { + return_if_found!(decl.header.search(ctx, searcher)); + return_if_found!(decl.parameter_list.search(ctx, searcher)); + decl.return_type.search(ctx, searcher) + } + SubprogramSpecification::Procedure(ref decl) => { + return_if_found!(decl.header.search(ctx, searcher)); + decl.parameter_list.search(ctx, searcher) + } + } +} + +impl Search for SubprogramHeader { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.generic_list.search(ctx, searcher)); + self.map_aspect.search(ctx, searcher) + } +} + +impl Search for LibraryClause { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + for name in self.name_list.iter() { + return_if_found!(searcher + .search_pos_with_ref(ctx, name.item.pos(ctx), &name.reference) + .or_not_found()); + } + NotFound + } +} + +impl Search for ContextItem { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_finished!(searcher.search_with_pos(ctx, &self.get_pos(ctx))); + match self { + ContextItem::Use(ref use_clause) => { + return_if_found!(use_clause.name_list.search(ctx, searcher)); + } + ContextItem::Library(ref library_clause) => { + return_if_found!(library_clause.search(ctx, searcher)); + } + ContextItem::Context(ref context_clause) => { + return_if_found!(context_clause.name_list.search(ctx, searcher)); + } + } + NotFound + } +} + +impl Search for AnyDesignUnit { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + delegate_any!(self, unit, unit.search(ctx, searcher)) + } +} + +impl Search for AnyPrimaryUnit { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + delegate_primary!(self, unit, unit.search(ctx, searcher)) + } +} + +impl Search for AnySecondaryUnit { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + delegate_secondary!(self, unit, unit.search(ctx, searcher)) + } +} + +impl Search for EntityDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.context_clause.search(ctx, searcher)); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::Entity(self)) + ) + .or_not_found()); + if let Some(clause) = &self.generic_clause { + return_if_found!(clause.search(ctx, searcher)); + } + if let Some(clause) = &self.port_clause { + return_if_found!(clause.search(ctx, searcher)); + } + return_if_found!(self.decl.search(ctx, searcher)); + self.statements.search(ctx, searcher) + } +} + +impl Search for ArchitectureBody { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.context_clause.search(ctx, searcher)); + return_if_found!(searcher + .search_ident_ref(ctx, &self.entity_name) + .or_not_found()); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::Architecture(self)) + ) + .or_not_found()); + return_if_found!(self.decl.search(ctx, searcher)); + self.statements.search(ctx, searcher) + } +} + +impl Search for PackageDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.context_clause.search(ctx, searcher)); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::Package(self)) + ) + .or_not_found()); + return_if_found!(self.generic_clause.search(ctx, searcher)); + self.decl.search(ctx, searcher) + } +} + +impl Search for PackageBody { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.context_clause.search(ctx, searcher)); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::PackageBody(self)) + ) + .or_not_found()); + self.decl.search(ctx, searcher) + } +} + +impl Search for PackageInstantiation { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.context_clause.search(ctx, searcher)); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::PackageInstance(self)) + ) + .or_not_found()); + return_if_found!(self.generic_map.search(ctx, searcher)); + self.package_name.search(ctx, searcher) + } +} + +impl Search for ConfigurationDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.context_clause.search(ctx, searcher)); + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::Configuration(self)) + ) + .or_not_found()); + self.entity_name.search(ctx, searcher) + } +} + +impl Search for ContextDeclaration { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new(&self.ident.decl, DeclarationItem::Context(self)) + ) + .or_not_found()); + self.items.search(ctx, searcher) + } +} + +impl Search for CaseStatement { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + let CaseStatement { + expression, + alternatives, + .. + } = self; + return_if_found!(expression.search(ctx, searcher)); + return_if_found!(search_alternatives(alternatives, false, searcher, ctx)); + NotFound + } +} + +impl Search for MapAspect { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + self.list.search(ctx, searcher) + } +} + +impl Search for SubprogramInstantiation { + fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(searcher + .search_decl( + ctx, + FoundDeclaration::new( + &self.ident.decl, + DeclarationItem::SubprogramInstantiation(self) + ) + ) + .or_not_found()); + return_if_found!(self.subprogram_name.search(ctx, searcher)); + if let Some(signature) = &self.signature { + return_if_found!(signature.item.search(ctx, searcher)); + } + self.generic_map.search(ctx, searcher) + } +} + +// Search for reference to declaration/definition at cursor +pub struct ItemAtCursor<'a> { + root: &'a DesignRoot, + cursor: Position, + pub result: Option<(SrcPos, EntRef<'a>)>, +} + +impl<'a> ItemAtCursor<'a> { + pub fn new(root: &'a DesignRoot, cursor: Position) -> Self { + ItemAtCursor { + root, + cursor, + result: None, + } + } + + fn is_inside(&self, pos: &SrcPos) -> bool { + // cursor is the gap between character cursor and cursor + 1 + // Thus cursor will match character cursor and cursor + 1 + pos.start() <= self.cursor && self.cursor <= pos.end() + } + + fn search_decl_pos(&mut self, pos: &SrcPos, ent: EntRef<'a>) -> SearchState { + if self.is_inside(pos) { + self.result = Some((pos.clone(), ent)); + Finished(Found) + } else { + NotFinished + } + } +} + +impl<'a> Searcher for ItemAtCursor<'a> { + fn search_with_pos(&mut self, _ctx: &dyn TokenAccess, pos: &SrcPos) -> SearchState { + // cursor is the gap between character cursor and cursor + 1 + // Thus cursor will match character cursor and cursor + 1 + if self.is_inside(pos) { + NotFinished + } else { + Finished(NotFound) + } + } + + fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + if let Some(id) = decl.ent_id() { + let ent = self.root.get_ent(id); + + if let Some(decl_pos) = ent.decl_pos() { + if let Finished(res) = self.search_decl_pos(decl_pos, ent) { + return Finished(res); + } + } + + if let Some(end_pos) = decl.end_ident_pos() { + return self.search_decl_pos(ctx.get_pos(end_pos), ent); + } + } + NotFinished + } + + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + pos: &SrcPos, + reference: &Reference, + ) -> SearchState { + if self.is_inside(pos) { + if let Some(id) = reference.get() { + self.result = Some((pos.clone(), self.root.get_ent(id))); + Finished(Found) + } else { + Finished(NotFound) + } + } else { + NotFinished + } + } +} + +// Search for reference to declaration/definition at cursor +pub struct FindEnt<'a, T: Fn(EntRef<'a>) -> bool> { + root: &'a DesignRoot, + cond: T, + pub result: Option>, +} + +impl<'a, T: Fn(EntRef<'a>) -> bool> FindEnt<'a, T> { + pub fn new(root: &'a DesignRoot, cond: T) -> FindEnt<'a, T> { + FindEnt { + root, + cond, + result: None, + } + } +} + +impl<'a, T: Fn(EntRef<'a>) -> bool> Searcher for FindEnt<'a, T> { + fn search_decl(&mut self, _ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + if let Some(id) = decl.ent_id() { + let ent = self.root.get_ent(id); + if (self.cond)(ent) { + self.result = Some(ent); + return SearchState::Finished(SearchResult::Found); + } + } + + SearchState::NotFinished + } +} + +pub struct FindAllEnt<'a, T: FnMut(EntRef<'a>) -> bool> { + root: &'a DesignRoot, + cond: T, + pub result: Vec>, +} + +impl<'a, T: FnMut(EntRef<'a>) -> bool> FindAllEnt<'a, T> { + pub fn new(root: &'a DesignRoot, cond: T) -> FindAllEnt<'a, T> { + FindAllEnt { + root, + cond, + result: Vec::default(), + } + } +} + +impl<'a, T: FnMut(EntRef<'a>) -> bool> Searcher for FindAllEnt<'a, T> { + fn search_decl(&mut self, _ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + if let Some(id) = decl.ent_id() { + let ent = self.root.get_ent(id); + if (self.cond)(ent) { + self.result.push(ent); + } + } + + SearchState::NotFinished + } +} + +// Search for a declaration/definition and format it +pub struct FormatDeclaration<'a> { + ent: EntRef<'a>, + pub result: Option, +} + +impl<'a> FormatDeclaration<'a> { + pub fn new(ent: EntRef<'a>) -> FormatDeclaration<'a> { + FormatDeclaration { ent, result: None } + } +} + +impl<'a> Searcher for FormatDeclaration<'a> { + fn search_decl(&mut self, _ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + let id = if let Some(id) = decl.ent_id() { + id + } else { + return NotFinished; + }; + + if self.ent.is_implicit_of(id) { + // Implicit + self.result = Some(format!( + "-- {}\n\n-- Implicitly defined by:\n{}\n", + self.ent.describe(), + decl.ast, + )); + return Finished(Found); + } else if self.ent.id() == id { + // Explicit + self.result = Some(decl.ast.to_string()); + return Finished(Found); + } + NotFinished + } +} + +// Search for all references to declaration/definition +pub struct FindAllReferences<'a> { + root: &'a DesignRoot, + ent: EntRef<'a>, + pub references: Vec, +} + +pub fn is_reference(ent: EntRef<'_>, other: EntRef<'_>) -> bool { + if ent.id() == other.id() { + return true; + } + + if ent.is_instance_of(other) || other.is_instance_of(ent) { + return true; + } + + if ent.is_declared_by(other) || other.is_declared_by(ent) { + return true; + } + + false +} + +impl<'a> FindAllReferences<'a> { + pub fn new(root: &'a DesignRoot, ent: EntRef<'a>) -> FindAllReferences<'a> { + FindAllReferences { + root, + ent, + references: Vec::new(), + } + } +} + +impl<'a> Searcher for FindAllReferences<'a> { + fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + if let Some(id) = decl.ent_id() { + let other = self.root.get_ent(id); + + if is_reference(self.ent, other) { + if let Some(decl_pos) = other.decl_pos() { + self.references.push(decl_pos.clone()); + } + if let Some(pos) = decl.end_ident_pos() { + self.references.push(ctx.get_pos(pos).clone()); + } + } + } + NotFinished + } + + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + pos: &SrcPos, + reference: &Reference, + ) -> SearchState { + if let Some(id) = reference.get() { + let other = self.root.get_ent(id); + if is_reference(self.ent, other) { + self.references.push(pos.clone()); + } + }; + NotFinished + } +} + +impl<'a> FoundDeclaration<'a> { + fn end_ident_pos(&self) -> Option { + match &self.ast { + DeclarationItem::InterfaceObject(_) => None, + DeclarationItem::ForIndex(..) => None, + DeclarationItem::ForGenerateIndex(..) => None, + DeclarationItem::Subprogram(value) => value.end_ident_pos, + DeclarationItem::SubprogramDecl(..) => None, + DeclarationItem::Object(..) => None, + DeclarationItem::ElementDeclaration(..) => None, + DeclarationItem::EnumerationLiteral(..) => None, + DeclarationItem::File(..) => None, + DeclarationItem::Type(value) => value.end_ident_pos, + DeclarationItem::InterfaceType(..) => None, + DeclarationItem::InterfacePackage(..) => None, + DeclarationItem::InterfaceFile(..) => None, + DeclarationItem::PhysicalTypePrimary(..) => None, + DeclarationItem::PhysicalTypeSecondary(..) => None, + DeclarationItem::Component(value) => value.end_ident_pos, + DeclarationItem::Attribute(..) => None, + DeclarationItem::Alias(..) => None, + DeclarationItem::Package(value) => value.end_ident_pos, + DeclarationItem::PackageBody(value) => value.end_ident_pos, + DeclarationItem::PackageInstance(..) => None, + DeclarationItem::Configuration(value) => value.end_ident_pos, + DeclarationItem::Entity(value) => value.end_ident_pos, + DeclarationItem::Architecture(value) => value.end_ident_pos, + DeclarationItem::Context(value) => value.end_ident_pos, + DeclarationItem::GenerateBody(..) => None, + DeclarationItem::ConcurrentStatement(..) => None, + DeclarationItem::SequentialStatement(..) => None, + DeclarationItem::SubprogramInstantiation(_) => None, + DeclarationItem::View(view) => view.end_ident_pos, + } + } + + fn ent_id_ref(&self) -> &Reference { + self.reference + } +} + +impl SubprogramSpecification { + fn ent_id_ref(&self) -> &Reference { + match self { + SubprogramSpecification::Procedure(proc) => &proc.designator.decl, + SubprogramSpecification::Function(func) => &func.designator.decl, + } + } +} + +impl<'a> HasEntityId for FoundDeclaration<'a> { + fn ent_id(&self) -> Option { + self.ent_id_ref().get() + } +} + +impl std::fmt::Display for DeclarationItem<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DeclarationItem::InterfaceObject(ref value) => match value.list_type { + InterfaceType::Port => write!(f, "port {value};"), + InterfaceType::Generic => write!(f, "generic {value};"), + InterfaceType::Parameter => write!(f, "{value};"), + }, + DeclarationItem::ForIndex(ref ident, ref drange) => { + write!(f, "for {ident} in {drange} loop") + } + DeclarationItem::ForGenerateIndex(ref ident, ref value) => match ident { + Some(ident) => write!(f, "{ident}: {value}"), + None => write!(f, "{value}"), + }, + DeclarationItem::Subprogram(value) => { + write!(f, "{};", value.specification) + } + DeclarationItem::SubprogramDecl(ref value) => { + write!(f, "{value}") + } + DeclarationItem::SubprogramInstantiation(ref value) => { + write!(f, "{value};") + } + DeclarationItem::Object(ref value) => { + write!(f, "{value}") + } + DeclarationItem::ElementDeclaration(elem) => { + write!(f, "{elem}") + } + DeclarationItem::EnumerationLiteral(ident, elem) => { + write!(f, "{elem} : {ident}") + } + DeclarationItem::File(ref value) => { + write!(f, "{value}") + } + DeclarationItem::PhysicalTypePrimary(ref value) => { + write!(f, "{value}") + } + DeclarationItem::PhysicalTypeSecondary(_, ref literal) => { + write!(f, "{literal}") + } + DeclarationItem::Type(ref value) => { + write!(f, "{value}") + } + DeclarationItem::InterfaceType(ref value) => { + write!(f, "type {value}") + } + DeclarationItem::InterfacePackage(value) => { + write!(f, "{value}") + } + DeclarationItem::InterfaceFile(value) => { + write!(f, "{value}") + } + DeclarationItem::Component(ref value) => { + write!(f, "{value}") + } + DeclarationItem::Alias(ref value) => { + write!(f, "{value}") + } + DeclarationItem::Attribute(ref value) => { + write!(f, "{value}") + } + DeclarationItem::Package(ref value) => { + write!(f, "{value}") + } + DeclarationItem::PackageBody(value) => { + // Will never be shown has hover will goto the declaration + write!(f, "package body {}", value.name()) + } + DeclarationItem::PackageInstance(ref value) => { + write!(f, "{value}") + } + DeclarationItem::Configuration(ref value) => { + write!(f, "{value}") + } + DeclarationItem::Entity(ref value) => { + write!(f, "{value}") + } + DeclarationItem::Architecture(value) => { + write!(f, "architecture {} of {}", value.ident(), value.entity_name) + } + DeclarationItem::Context(ref value) => { + write!(f, "{value}") + } + DeclarationItem::GenerateBody(value) => { + write!(f, "{value}") + } + DeclarationItem::ConcurrentStatement(value) => { + if let Some(ref label) = value.label.tree { + write!(f, "{label}") + } else { + write!(f, "") + } + } + DeclarationItem::SequentialStatement(value) => { + if let Some(ref label) = value.label.tree { + write!(f, "{label}") + } else { + write!(f, "") + } + } + DeclarationItem::View(value) => write!(f, "view {} of {}", value.ident, value.typ), + } + } +} + +#[derive(Default)] +pub struct FindAllUnresolved { + pub count: usize, + pub unresolved: Vec, +} + +impl Searcher for FindAllUnresolved { + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + pos: &SrcPos, + reference: &Reference, + ) -> SearchState { + self.count += 1; + if reference.is_undefined() { + self.unresolved.push(pos.clone()); + } + NotFinished + } +} + +pub fn clear_references(tree: &mut impl Search, ctx: &dyn TokenAccess) { + struct ReferenceClearer; + + impl Searcher for ReferenceClearer { + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + _pos: &SrcPos, + reference: &Reference, + ) -> SearchState { + reference.clear(); + NotFinished + } + + fn search_decl( + &mut self, + _ctx: &dyn TokenAccess, + decl: FoundDeclaration<'_>, + ) -> SearchState { + let reference = decl.ent_id_ref(); + reference.clear(); + NotFinished + } + } + + let mut searcher = ReferenceClearer; + let _ = tree.search(ctx, &mut searcher); +} + +#[cfg(test)] +#[allow(clippy::ptr_arg)] +pub fn check_no_unresolved(tree: &mut impl Search, tokens: &Vec) { + #[derive(Default)] + struct CheckNoUnresolved; + + impl Searcher for CheckNoUnresolved { + fn search_pos_with_ref( + &mut self, + _ctx: &dyn TokenAccess, + _pos: &SrcPos, + reference: &Reference, + ) -> SearchState { + assert!(reference.is_defined()); + NotFinished + } + } + + let mut searcher = CheckNoUnresolved; + let _ = tree.search(tokens, &mut searcher); +} diff --git a/vhdl_lang/src/ast/token_range.rs b/vhdl_lang/src/ast/token_range.rs new file mode 100644 index 0000000..26ed33a --- /dev/null +++ b/vhdl_lang/src/ast/token_range.rs @@ -0,0 +1,121 @@ +/// For most applications in the context of a language server, +/// the lexical position (i.e., a position in the source code) +/// of all AST nodes must be known. +/// In the context of `vhdl_lang`, this information is provided +/// using Token information. Each AST element knows the token span that it was declared in. +/// Information, such as the position can be queried using the `pos(TokenAccess)` method. +/// A [TokenAccess] is a context object that is passed in all relevant operations +/// (i.e., when traversing the AST using the [Search] trait +/// or when getting the source code information when generating code outlines in a language server). +/// This is also the mechanic used to extract supplementary information, such as comments for +/// documentation generation. +use crate::{SrcPos, TokenAccess, TokenId, TokenSpan}; + +/// A struct that associates some generic item to a single token. +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct WithToken { + pub item: T, + pub token: TokenId, +} + +impl WithToken { + pub fn new(item: T, token: TokenId) -> WithToken { + WithToken { item, token } + } + + /// Retrieves the position of this object using the provided `TokenAccess`. + pub fn pos<'a>(&'a self, ctx: &'a dyn TokenAccess) -> &'a SrcPos { + ctx.get_pos(self.token) + } + + /// Maps this element into another element applying the given function + /// but retaining the source location (i.e., the token). + pub(crate) fn map_into(self, f: F) -> WithToken + where + F: FnOnce(T) -> U, + { + WithToken { + item: f(self.item), + token: self.token, + } + } + + /// Maps this element into another element applying the given function + /// but retaining the source location. + /// The returned object's `TokenSpan` will have this token id as start and end. + pub(crate) fn map_into_span(self, f: F) -> WithTokenSpan + where + F: FnOnce(T) -> U, + { + WithTokenSpan { + item: f(self.item), + span: self.token.into(), + } + } +} + +/// A struct that associates some generic item to a contiguous span of tokens. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct WithTokenSpan { + pub item: T, + pub span: TokenSpan, +} + +impl WithTokenSpan { + pub fn new(item: T, span: TokenSpan) -> WithTokenSpan { + WithTokenSpan { item, span } + } + + pub fn from(item: T, span: impl Into) -> WithTokenSpan { + WithTokenSpan { + item, + span: span.into(), + } + } + + /// Retrieves the position of this object using the provided `TokenAccess`. + pub fn pos(&self, ctx: &dyn TokenAccess) -> SrcPos { + self.span.pos(ctx) + } + + /// Maps this element into another element applying the given function + /// but retaining the source location (i.e., the token span). + pub(crate) fn map_into(self, f: F) -> WithTokenSpan + where + F: FnOnce(T) -> U, + { + WithTokenSpan { + item: f(self.item), + span: self.span, + } + } + + /// Attempts to map this element into another element applying the given function. + /// If the function returns `None`, this will also return `None`. + /// Otherwise, the semantics are the same as [map_into](WithTokenSpan::map_into) + pub(crate) fn try_map_into(self, f: F) -> Option> + where + F: FnOnce(T) -> Option, + { + Some(WithTokenSpan { + item: f(self.item)?, + span: self.span, + }) + } + + /// Returns a new `WithTokenSpan` object that encompasses this item + /// but extends the token span starting with the given token. + pub(crate) fn start_with(self, id: TokenId) -> Self { + WithTokenSpan { + item: self.item, + span: self.span.start_with(id), + } + } + + pub fn as_ref(&self) -> WithTokenSpan<&T> { + WithTokenSpan { + item: &self.item, + span: self.span, + } + } +} diff --git a/vhdl_lang/src/ast/util.rs b/vhdl_lang/src/ast/util.rs new file mode 100644 index 0000000..02d1f6b --- /dev/null +++ b/vhdl_lang/src/ast/util.rs @@ -0,0 +1,564 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +//! Name conversions +use super::*; +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::named_entity::{Concurrent, Sequential}; +use crate::TokenSpan; + +impl WithTokenSpan { + pub fn suffix_pos(&self) -> TokenSpan { + match self.item { + Name::Designator(..) => self.span, + Name::Selected(_, ref suffix) => suffix.token.into(), + // @TODO add pos of .all? + Name::SelectedAll(ref prefix) => prefix.span, + Name::CallOrIndexed(ref fcall) => fcall.name.span, + Name::Slice(ref prefix, ..) => prefix.span, + Name::Attribute(ref attr, ..) => attr.name.span, + Name::External(..) => self.span, + } + } +} + +pub fn to_simple_name(ctx: &dyn TokenAccess, name: WithTokenSpan) -> DiagnosticResult { + match name.item { + Name::Designator(WithRef { + item: Designator::Identifier(ident), + .. + }) => Ok(WithToken { + item: ident, + token: name.span.start_token, + }), + _ => Err(Diagnostic::new( + name.span.pos(ctx), + "Expected simple name", + ErrorCode::SyntaxError, + )), + } +} + +pub fn as_simple_name_mut(name: &mut Name) -> Option<&mut WithRef> { + match name { + Name::Designator( + des @ WithRef { + item: Designator::Identifier(_), + .. + }, + ) => Some(des), + _ => None, + } +} + +pub fn as_name_mut(expr: &mut Expression) -> Option<&mut Name> { + match expr { + Expression::Name(name) => Some(name.as_mut()), + _ => None, + } +} + +pub trait HasDesignator { + fn designator(&self) -> &Designator; +} + +impl HasDesignator for WithTokenSpan { + fn designator(&self) -> &Designator { + self.item.designator() + } +} + +impl HasDesignator for Designator { + fn designator(&self) -> &Designator { + self + } +} + +impl HasDesignator for WithRef { + fn designator(&self) -> &Designator { + self.item.designator() + } +} + +impl HasIdent for WithRef { + fn ident(&self) -> &Ident { + &self.item + } +} + +impl Designator { + pub fn into_ref(self) -> WithRef { + WithRef::new(self) + } +} + +impl Ident { + pub fn into_ref(self) -> WithRef { + WithRef::new(self) + } +} + +impl WithTokenSpan { + pub fn into_ref(self) -> WithTokenSpan> { + self.map_into(|name| name.into_ref()) + } +} + +impl WithToken { + pub fn into_ref(self) -> WithToken> { + self.map_into(|name| name.into_ref()) + } +} + +pub trait HasIdent { + fn ident(&self) -> &Ident; + fn name(&self) -> &Symbol { + &self.ident().item + } + + fn ident_pos<'a>(&'a self, ctx: &'a dyn TokenAccess) -> &SrcPos { + self.ident().pos(ctx) + } +} + +impl HasIdent for Ident { + fn ident(&self) -> &Ident { + self + } +} + +impl HasIdent for WithDecl { + fn ident(&self) -> &Ident { + self.tree.ident() + } +} + +impl HasIdent for EntityDeclaration { + fn ident(&self) -> &Ident { + self.ident.ident() + } +} + +impl HasIdent for PackageDeclaration { + fn ident(&self) -> &Ident { + self.ident.ident() + } +} + +impl HasIdent for PackageBody { + fn ident(&self) -> &Ident { + &self.ident.tree + } +} + +impl HasIdent for ArchitectureBody { + fn ident(&self) -> &Ident { + self.ident.ident() + } +} + +impl HasIdent for PackageInstantiation { + fn ident(&self) -> &Ident { + self.ident.ident() + } +} + +impl HasIdent for ContextDeclaration { + fn ident(&self) -> &Ident { + self.ident.ident() + } +} + +impl HasIdent for ConfigurationDeclaration { + fn ident(&self) -> &Ident { + self.ident.ident() + } +} + +impl HasIdent for AnyPrimaryUnit { + fn ident(&self) -> &Ident { + match self { + AnyPrimaryUnit::Entity(ref unit) => unit.ident(), + AnyPrimaryUnit::Configuration(ref unit) => unit.ident(), + AnyPrimaryUnit::Package(ref unit) => unit.ident(), + AnyPrimaryUnit::PackageInstance(ref unit) => unit.ident(), + AnyPrimaryUnit::Context(ref unit) => unit.ident(), + } + } +} + +impl HasIdent for AnySecondaryUnit { + fn ident(&self) -> &Ident { + match self { + AnySecondaryUnit::PackageBody(ref unit) => unit.ident(), + AnySecondaryUnit::Architecture(ref unit) => unit.ident(), + } + } +} + +impl HasIdent for AnyDesignUnit { + fn ident(&self) -> &Ident { + match self { + AnyDesignUnit::Primary(ref unit) => unit.ident(), + AnyDesignUnit::Secondary(ref unit) => unit.ident(), + } + } +} + +impl<'a, T: HasIdent> From<&'a T> for WithToken { + fn from(other: &'a T) -> WithToken { + other.ident().to_owned().map_into(Designator::Identifier) + } +} + +/// Primary identifier in secondary units +pub trait HasPrimaryIdent { + fn primary_ident(&self) -> &Ident; + fn primary_name(&self) -> &Symbol { + &self.primary_ident().item + } +} + +impl HasPrimaryIdent for ArchitectureBody { + fn primary_ident(&self) -> &Ident { + &self.entity_name.item + } +} + +impl HasPrimaryIdent for PackageBody { + fn primary_ident(&self) -> &Ident { + &self.ident.tree + } +} + +impl HasPrimaryIdent for AnySecondaryUnit { + fn primary_ident(&self) -> &Ident { + match self { + AnySecondaryUnit::Architecture(unit) => unit.primary_ident(), + AnySecondaryUnit::PackageBody(unit) => unit.primary_ident(), + } + } +} + +impl From for Designator { + fn from(other: EnumerationLiteral) -> Designator { + match other { + EnumerationLiteral::Identifier(ident) => Designator::Identifier(ident), + EnumerationLiteral::Character(byte) => Designator::Character(byte), + } + } +} + +impl From for Designator { + fn from(other: Symbol) -> Designator { + Designator::Identifier(other) + } +} + +impl From> for WithTokenSpan { + fn from(other: WithTokenSpan) -> WithTokenSpan { + other.map_into(|sym| sym.into()) + } +} + +impl<'a> From<&'a Symbol> for Designator { + fn from(other: &'a Symbol) -> Designator { + other.clone().into() + } +} + +impl SubprogramDesignator { + pub fn into_designator(self) -> Designator { + match self { + SubprogramDesignator::Identifier(ident) => Designator::Identifier(ident), + SubprogramDesignator::OperatorSymbol(ident) => Designator::OperatorSymbol(ident), + } + } +} + +impl SubprogramSpecification { + pub fn token(&self) -> TokenId { + match self { + SubprogramSpecification::Function(ref function) => function.designator.tree.token, + SubprogramSpecification::Procedure(ref procedure) => procedure.designator.tree.token, + } + } +} + +impl EnumerationLiteral { + pub fn into_designator(self) -> Designator { + match self { + EnumerationLiteral::Identifier(ident) => Designator::Identifier(ident), + EnumerationLiteral::Character(byte) => Designator::Character(byte), + } + } +} + +impl Designator { + pub fn as_identifier(&self) -> Option<&Symbol> { + if let Designator::Identifier(sym) = self { + Some(sym) + } else { + None + } + } + + pub fn expect_identifier(&self) -> &Symbol { + self.as_identifier().unwrap() + } + + pub fn describe(&self) -> String { + match self { + Designator::Character(chr) => format!("'{chr}'"), + Designator::Identifier(ident) => format!("'{ident}'"), + Designator::OperatorSymbol(op) => format!("operator \"{op}\""), + Designator::Anonymous(_) => "".to_owned(), + } + } +} + +impl Name { + pub fn suffix_reference_mut(&mut self) -> Option<&mut Reference> { + match self { + Name::Designator(suffix) => Some(&mut suffix.reference), + Name::Selected(_, suffix) => Some(&mut suffix.item.reference), + _ => None, + } + } + + // Get an already set suffix reference such as when an ambiguous overloaded call has already been resolved + pub fn get_suffix_reference(&self) -> Option { + match self { + Name::Designator(suffix) => suffix.reference.get(), + Name::Selected(_, suffix) => suffix.item.reference.get(), + _ => None, + } + } + + pub fn prefix(&self) -> Option<&Designator> { + match self { + Self::Attribute(attr) => attr.name.item.prefix(), + Self::Designator(d) => Some(d.designator()), + Self::External(..) => None, + Self::CallOrIndexed(fcall) => fcall.name.item.prefix(), + Self::SelectedAll(name) => name.item.prefix(), + Self::Selected(name, ..) => name.item.prefix(), + Self::Slice(name, ..) => name.item.prefix(), + } + } + + /// Returns true if the name is purely a selected name + /// Example: a.b.c + pub fn is_selected_name(&self) -> bool { + match self { + Name::Designator(_) => true, + Name::Selected(prefix, _) => prefix.item.is_selected_name(), + _ => false, + } + } +} + +impl CallOrIndexed { + // During parsing function calls and indexed names are ambiguous + // Thus we convert function calls to indexed names during the analysis stage + pub fn as_indexed(&mut self) -> Option> { + if !self.could_be_indexed_name() { + return None; + } + + let CallOrIndexed { + ref mut name, + ref mut parameters, + } = self; + + let mut indexes: Vec> = Vec::with_capacity(parameters.items.len()); + + for elem in parameters.items.iter_mut() { + if let ActualPart::Expression(ref mut expr) = &mut elem.actual.item { + indexes.push(Index { + pos: elem.actual.span, + expr, + }); + } + } + + Some(IndexedName { name, indexes }) + } + + pub fn could_be_indexed_name(&self) -> bool { + self.parameters + .items + .iter() + .all(|assoc| assoc.formal.is_none() && !matches!(assoc.actual.item, ActualPart::Open)) + } +} + +pub struct IndexedName<'a> { + pub name: &'a mut WithTokenSpan, + pub indexes: Vec>, +} + +pub struct Index<'a> { + pub pos: TokenSpan, + pub expr: &'a mut Expression, +} + +impl AttributeName { + pub fn as_range(&self) -> Option { + if let AttributeDesignator::Range(r) = self.attr.item { + Some(r) + } else { + None + } + } + + pub fn as_type(&self) -> Option { + if self.signature.is_none() && self.expr.is_none() { + if let AttributeDesignator::Type(t) = self.attr.item { + Some(t) + } else { + None + } + } else { + None + } + } +} + +impl RangeConstraint { + pub fn span(&self) -> TokenSpan { + self.left_expr.span.combine(self.right_expr.span) + } +} + +impl crate::ast::Range { + pub fn span(&self) -> TokenSpan { + use crate::ast::Range::*; + match self { + Range(constraint) => constraint.span(), + Attribute(attr) => attr.name.span.end_with(attr.attr.token), + } + } +} + +impl DiscreteRange { + pub fn span(&self) -> TokenSpan { + match self { + DiscreteRange::Discrete(type_mark, _) => type_mark.span, + DiscreteRange::Range(range) => range.span(), + } + } +} + +impl SubprogramSpecification { + pub fn subpgm_designator(&self) -> &WithToken { + match self { + SubprogramSpecification::Procedure(s) => &s.designator.tree, + SubprogramSpecification::Function(s) => &s.designator.tree, + } + } + + pub fn reference_mut(&mut self) -> &mut Reference { + match self { + SubprogramSpecification::Function(ref mut function) => &mut function.designator.decl, + SubprogramSpecification::Procedure(ref mut procedure) => &mut procedure.designator.decl, + } + } +} + +impl SubprogramDeclaration { + pub fn subpgm_designator(&self) -> &WithToken { + self.specification.subpgm_designator() + } + + pub fn reference_mut(&mut self) -> &mut Reference { + self.specification.reference_mut() + } +} + +impl ConcurrentStatement { + pub fn label_typ(&self) -> Option { + use ConcurrentStatement::*; + match self { + ProcedureCall(_) => None, + Block(_) => Some(Concurrent::Block), + Process(_) => Some(Concurrent::Process), + Assert(_) => None, + Assignment(_) => None, + Instance(_) => Some(Concurrent::Instance), + ForGenerate(_) | IfGenerate(_) | CaseGenerate(_) => Some(Concurrent::Generate), + } + } + + pub fn end_label_pos(&self) -> Option<&SrcPos> { + use ConcurrentStatement::*; + + match self { + ProcedureCall(_) => None, + Block(value) => value.end_label_pos.as_ref(), + Process(value) => value.end_label_pos.as_ref(), + Assert(_) => None, + Assignment(_) => None, + Instance(_) => None, + ForGenerate(value) => value.end_label_pos.as_ref(), + IfGenerate(value) => value.end_label_pos.as_ref(), + CaseGenerate(value) => value.end_label_pos.as_ref(), + } + } + + pub fn can_have_label(&self) -> bool { + self.label_typ().is_some() + } +} + +impl SequentialStatement { + pub fn label_typ(&self) -> Option { + use SequentialStatement::*; + match self { + Wait(_) => None, + Assert(_) => None, + Report(_) => None, + VariableAssignment(_) => None, + SignalAssignment(_) => None, + SignalForceAssignment(_) => None, + SignalReleaseAssignment(_) => None, + ProcedureCall(_) => None, + If(_) => Some(Sequential::If), + Case(_) => Some(Sequential::Case), + Loop(_) => Some(Sequential::Loop), + Next(_) => None, + Exit(_) => None, + Return(_) => None, + Null => None, + } + } + + pub fn end_label_pos(&self) -> Option<&SrcPos> { + use SequentialStatement::*; + match self { + Wait(_) => None, + Assert(_) => None, + Report(_) => None, + VariableAssignment(_) => None, + SignalAssignment(_) => None, + SignalForceAssignment(_) => None, + SignalReleaseAssignment(_) => None, + ProcedureCall(_) => None, + If(value) => value.end_label_pos.as_ref(), + Case(value) => value.end_label_pos.as_ref(), + Loop(value) => value.end_label_pos.as_ref(), + Next(_) => None, + Exit(_) => None, + Return(_) => None, + Null => None, + } + } + + pub fn can_have_label(&self) -> bool { + self.label_typ().is_some() + } +} diff --git a/vhdl_lang/src/completion.rs b/vhdl_lang/src/completion.rs new file mode 100644 index 0000000..5304da1 --- /dev/null +++ b/vhdl_lang/src/completion.rs @@ -0,0 +1,113 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com + +use crate::analysis::DesignRoot; +use crate::ast::{AttributeDesignator, Designator}; +use crate::completion::attributes::completions_for_attribute_name; +use crate::completion::generic::generic_completions; +use crate::completion::libraries::list_all_libraries; +use crate::completion::map_aspect::completions_for_map_aspect; +use crate::completion::selected::completions_for_selected_name; +use crate::completion::tokenizer::tokenize_input; +use crate::syntax::Kind; +use crate::{EntRef, Position, Source}; + +mod attributes; +mod entity_instantiation; +mod generic; +mod libraries; +mod map_aspect; +mod region; +mod selected; +mod tokenizer; + +#[derive(Debug, PartialEq, Clone)] +pub enum CompletionItem<'a> { + /// Simply complete the entities + /// e.g., `use std.` should simply list all elements in the std library + Simple(EntRef<'a>), + /// Formal parameter, e.g., in a port map + /// `port map (` might choose to complete ` => $1` + Formal(EntRef<'a>), + /// Multiple overloaded items are applicable. + /// The argument is the count of overloaded items in total. + Overloaded(Designator, usize), + /// Complete a keyword + Keyword(Kind), + /// Complete the 'work' library. + /// This is handled in a special manner because the + /// actual work library (using [CompletionItem::Simple] would complete the actual name + /// of the library, not the string 'work'. + Work, + /// Entity or component instantiation, i.e., + /// ```vhdl + /// my_ent: entity work.foo + /// generic map ( + /// -- ... + /// ) + /// port map ( + /// -- ... + /// ); + /// ``` + /// + /// The second argument is a vector of architectures that are associated + /// to the entity, if the first argument is an entity. + /// + /// For a component instantiation, the first argument is a reference to the + /// component. The second argument will always be empty. + Instantiation(EntRef<'a>, Vec>), + /// Complete an attribute designator (i.e. `'range`, `'stable`, ...) + Attribute(AttributeDesignator), +} + +macro_rules! kind { + ($kind: pat) => { + crate::syntax::Token { kind: $kind, .. } + }; +} + +/// Main entry point for completion. Given a source-file and a cursor position, +/// lists available completion options at the cursor position. +pub fn list_completion_options<'a>( + root: &'a DesignRoot, + source: &Source, + cursor: Position, +) -> Vec> { + use crate::syntax::Kind::*; + let tokens = tokenize_input(root.symbols(), source, cursor); + match &tokens[..] { + // With the current implementation of completions, this is annoying, rather than helpful. + // SemiColons will try to complete the ';' character, which when pressing enter will cause + // ';' to appear instead of a simple ; character. + // Therefore, we do not return any completions here. + [.., kind!(SemiColon)] => vec![], + [.., kind!(Library)] + | [.., kind!(Library), kind!(Identifier)] + | [.., kind!(Use)] + | [.., kind!(Use), kind!(Identifier)] => list_all_libraries(root), + [.., token, kind!(Dot)] | [.., token, kind!(Dot), kind!(Identifier)] => { + // get the entity before the token. + // We rely on the syntax parsing to be resilient enough for this to yield a reasonable value. + // Otherwise, we just return an empty value. + if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { + completions_for_selected_name(root, ent) + } else { + vec![] + } + } + [.., token, kind!(Tick)] | [.., token, kind!(Tick), kind!(Identifier)] => { + if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { + completions_for_attribute_name(ent) + } else { + vec![] + } + } + [.., kind!(LeftPar | Comma)] | [.., kind!(LeftPar | Comma), kind!(Identifier)] => { + completions_for_map_aspect(root, cursor, source) + } + _ => generic_completions(root, cursor, source), + } +} diff --git a/vhdl_lang/src/completion/attributes.rs b/vhdl_lang/src/completion/attributes.rs new file mode 100644 index 0000000..f51b1fe --- /dev/null +++ b/vhdl_lang/src/completion/attributes.rs @@ -0,0 +1,189 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::ast::{AttributeDesignator, ObjectClass, RangeAttribute, TypeAttribute}; +use crate::{named_entity, AnyEntKind, CompletionItem, EntRef, Object}; + +/// Produces completions for an attribute name, i.e., +/// `foo'` +/// The provided ent is the entity directly before the tick, i.e., +/// `foo` in the example above. +pub(crate) fn completions_for_attribute_name(ent: EntRef<'_>) -> Vec> { + let mut attributes: Vec = Vec::new(); + attributes.extend([ + AttributeDesignator::SimpleName, + AttributeDesignator::InstanceName, + AttributeDesignator::PathName, + ]); + + match ent.kind() { + AnyEntKind::Type(typ) => extend_attributes_of_type(typ, &mut attributes), + AnyEntKind::Object(obj) => extend_attributes_of_objects(obj, &mut attributes), + AnyEntKind::View(_) => attributes.push(AttributeDesignator::Converse), + _ => {} + } + attributes + .into_iter() + .map(CompletionItem::Attribute) + .chain( + ent.attrs + .values() + .map(|(_, b)| b) + .map(|ent| CompletionItem::Simple(ent.ent)), + ) + .collect() +} + +/// Extends applicable attributes when the attribute name is a type. +fn extend_attributes_of_type( + typ: &named_entity::Type<'_>, + attributes: &mut Vec, +) { + use AttributeDesignator::*; + if typ.is_scalar() { + attributes.extend([Left, Right, Low, High, Ascending, Image, Value]); + } else if typ.is_array() { + attributes.extend([ + Left, + Right, + Low, + High, + Range(RangeAttribute::Range), + Range(RangeAttribute::ReverseRange), + Length, + Ascending, + Type(TypeAttribute::Element), + ]); + } + if typ.is_discrete() { + attributes.extend([Pos, Val, Succ, Pred, LeftOf, RightOf]); + } +} + +/// Extends applicable attributes when the attribute name is an object. +fn extend_attributes_of_objects(obj: &Object<'_>, attributes: &mut Vec) { + extend_attributes_of_type(obj.subtype.type_mark().base_type().kind(), attributes); + attributes.push(AttributeDesignator::Type(TypeAttribute::Subtype)); + if obj.class == ObjectClass::Signal { + use crate::ast::SignalAttribute::*; + attributes.extend( + [ + Delayed, + Stable, + Quiet, + Transaction, + Event, + Active, + LastEvent, + LastActive, + LastValue, + Driving, + DrivingValue, + ] + .map(AttributeDesignator::Signal), + ); + } +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::LibraryBuilder; + use crate::ast::RangeAttribute; + use crate::list_completion_options; + use crate::syntax::test::assert_eq_unordered; + use crate::CompletionItem; + + #[test] + pub fn completes_attributes() { + use crate::ast::AttributeDesignator::*; + use crate::ast::TypeAttribute::*; + + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +package my_pkg is + constant foo : BIT_VECTOR := \"001\"; + constant bar: NATURAL := foo' +end package; +", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("foo'").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let expected_options = [ + Type(Element), + Type(Subtype), + Range(RangeAttribute::Range), + Range(RangeAttribute::ReverseRange), + Ascending, + Left, + Right, + High, + Low, + Length, + InstanceName, + SimpleName, + PathName, + ] + .map(CompletionItem::Attribute); + + assert_eq_unordered(&options, &expected_options); + } + + #[test] + pub fn completes_signal_attributes() { + use crate::ast::AttributeDesignator::*; + use crate::ast::SignalAttribute::*; + use crate::ast::TypeAttribute::*; + + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +package my_pkg is + signal foo : BIT_VECTOR := \"001\"; + signal bar: NATURAL := foo' +end package; +", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("foo'").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let expected_options = [ + Type(Element), + Type(Subtype), + Range(RangeAttribute::Range), + Range(RangeAttribute::ReverseRange), + Signal(Delayed), + Signal(Stable), + Signal(Quiet), + Signal(Transaction), + Signal(Event), + Signal(Active), + Signal(LastEvent), + Signal(LastActive), + Signal(LastValue), + Signal(Driving), + Signal(DrivingValue), + Ascending, + Left, + Right, + High, + Low, + Length, + InstanceName, + SimpleName, + PathName, + ] + .map(CompletionItem::Attribute); + + assert_eq_unordered(&options, &expected_options); + } +} diff --git a/vhdl_lang/src/completion/entity_instantiation.rs b/vhdl_lang/src/completion/entity_instantiation.rs new file mode 100644 index 0000000..ed1a72e --- /dev/null +++ b/vhdl_lang/src/completion/entity_instantiation.rs @@ -0,0 +1,369 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::completion::region::any_ent_to_completion_item; +use crate::named_entity::DesignEnt; +use crate::{AnyEntKind, CompletionItem, Design, EntRef, EntityId, HasEntityId}; +use itertools::Itertools; +use std::collections::HashSet; + +/// List all entities that are visible from a VHDL architecture and return +/// these entities as completion item. +pub(crate) fn get_visible_entities_from_architecture<'a>( + root: &'a DesignRoot, + ent: &DesignEnt<'a>, +) -> Vec> { + let mut entities: HashSet = HashSet::new(); + if let Design::Architecture(vis, _, ent_of_arch) = ent.kind() { + for ent_ref in vis.visible() { + match ent_ref.kind() { + AnyEntKind::Design(Design::Entity(..)) => { + entities.insert(ent_ref.id()); + } + AnyEntKind::Library => { + let Some(name) = ent_ref.library_name() else { + continue; + }; + let Some(lib) = root.get_lib(name) else { + continue; + }; + let itr = lib + .units() + .flat_map(|locked_unit| locked_unit.unit.get()) + .filter(|design_unit| design_unit.is_entity()) + .flat_map(|design_unit| design_unit.ent_id()) + .filter(|id| id != &ent_of_arch.id()); // Remove the entity that belongs to this architecture + entities.extend(itr); + } + _ => {} + } + } + } + entities + .into_iter() + .map(|eid| any_ent_to_completion_item(root.get_ent(eid), root)) + .collect_vec() +} + +/// Returns a vec populated with all architectures that belong to a given entity +pub(crate) fn get_architectures_for_entity<'a>( + ent: EntRef<'a>, + root: &'a DesignRoot, +) -> Vec> { + let Some(lib_symbol) = ent.library_name() else { + return vec![]; + }; + let Some(lib) = root.get_lib(lib_symbol) else { + return vec![]; + }; + let Some(sym) = ent.designator().as_identifier() else { + return vec![]; + }; + lib.secondary_units(sym) + .filter_map(|locked_unit| locked_unit.unit.get()) + .filter_map(|read_guard| read_guard.ent_id()) + .map(|eid| root.get_ent(eid)) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{assert_eq_unordered, check_no_diagnostics, LibraryBuilder}; + use crate::{list_completion_options, CompletionItem}; + use itertools::Itertools; + + #[test] + fn complete_entities() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity my_ent is +end my_ent; + +entity my_other_ent is +end my_other_ent; + +entity my_third_ent is +end my_third_ent; + +architecture arch of my_third_ent is +begin +end arch; + ", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("begin").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let my_ent = root + .search_reference(code.source(), code.s1("my_ent").start()) + .unwrap(); + + let my_other_ent = root + .search_reference(code.source(), code.s1("my_other_ent").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::Instantiation(my_ent, vec![]))); + assert!(options.contains(&CompletionItem::Instantiation(my_other_ent, vec![]))); + } + + #[test] + fn does_not_complete_in_architecture_declarative_part() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity my_ent is +end my_ent; + +entity my_other_ent is +end my_other_ent; + +entity my_third_ent is +end my_third_ent; + +architecture arch of my_third_ent is + +begin +end arch; + ", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s("is", 4).end(); + let options = list_completion_options(&root, code.source(), cursor); + + let my_ent = root + .search_reference(code.source(), code.s1("my_ent").start()) + .unwrap(); + + let my_other_ent = root + .search_reference(code.source(), code.s1("my_other_ent").start()) + .unwrap(); + + assert!(!options.contains(&CompletionItem::Instantiation(my_ent, vec![]))); + assert!(!options.contains(&CompletionItem::Instantiation(my_other_ent, vec![]))); + } + + #[test] + fn complete_entities_from_different_libraries() { + let mut builder = LibraryBuilder::new(); + let code1 = builder.code( + "libA", + "\ +entity my_ent is +end my_ent; + ", + ); + + let code2 = builder.code( + "libB", + "\ +entity my_ent2 is +end my_ent2; + +entity my_ent3 is +end my_ent3; + +architecture arch of my_ent3 is +begin + +end arch; + + ", + ); + + let code3 = builder.code( + "libC", + "\ +entity my_ent2 is +end my_ent2; + +library libA; + +entity my_ent3 is +end my_ent3; + +architecture arch of my_ent3 is +begin + +end arch; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag[..]); + let cursor = code2.s1("begin").end(); + let options = list_completion_options(&root, code2.source(), cursor); + + let my_ent2 = root + .search_reference(code2.source(), code2.s1("my_ent2").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::Instantiation(my_ent2, vec![]))); + + let ent1 = root + .search_reference(code1.source(), code1.s1("my_ent").start()) + .unwrap(); + + let cursor = code3.s1("begin").end(); + let options = list_completion_options(&root, code3.source(), cursor); + + let my_ent2 = root + .search_reference(code3.source(), code3.s1("my_ent2").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::Instantiation(my_ent2, vec![]))); + assert!(options.contains(&CompletionItem::Instantiation(ent1, vec![]))); + } + + #[test] + pub fn entity_with_two_architecture() { + let mut builder = LibraryBuilder::new(); + let code1 = builder.code( + "libA", + "\ +entity my_ent is +end my_ent; + +architecture arch1 of my_ent is +begin +end arch1; + +architecture arch2 of my_ent is +begin +end arch2; + ", + ); + let code2 = builder.code( + "libA", + "\ +entity my_ent2 is +end my_ent2; + +architecture arch of my_ent2 is +begin + +end arch; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag[..]); + let cursor = code2.s("begin", 1).end(); + let options = list_completion_options(&root, code2.source(), cursor); + + let ent = root + .search_reference(code1.source(), code1.s1("my_ent").start()) + .unwrap(); + + let arch1 = root + .search_reference(code1.source(), code1.s1("arch1").start()) + .unwrap(); + + let arch2 = root + .search_reference(code1.source(), code1.s1("arch2").start()) + .unwrap(); + + let applicable_options = options + .into_iter() + .filter_map(|option| match option { + CompletionItem::Instantiation(ent, architectures) => Some((ent, architectures)), + _ => None, + }) + .collect_vec(); + // println!("{:?}", applicable_options); + match &applicable_options[..] { + [(got_ent, architectures)] => { + pretty_assertions::assert_eq!(*got_ent, ent); + assert_eq_unordered(architectures, &[arch1, arch2]); + } + _ => panic!("Expected entity instantiation"), + } + } + + #[test] + fn component_instantiations() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +package foo is + component comp_A is + end component; +end foo; + +use work.foo.all; + +entity my_ent is +end my_ent; + +architecture arch1 of my_ent is + component comp_B is + end component; + + component comp_C is + end component; +begin +end arch1; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag[..]); + let cursor = code.s1("begin").end(); + let options = list_completion_options(&root, code.source(), cursor); + + for component in ["comp_A", "comp_B", "comp_C"] { + let entity = root + .search_reference(code.source(), code.s1(component).start()) + .unwrap(); + assert!(options.contains(&CompletionItem::Instantiation(entity, vec![]))) + } + } + + #[test] + fn complete_entities_in_block() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity my_ent is +end my_ent; + +entity my_other_ent is +end my_other_ent; + +entity my_third_ent is +end my_third_ent; + +architecture arch of my_third_ent is +begin + foo: block is + begin + end block foo; +end arch; + ", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s("begin", 2).end(); + let options = list_completion_options(&root, code.source(), cursor); + + let my_ent = root + .search_reference(code.source(), code.s1("my_ent").start()) + .unwrap(); + + let my_other_ent = root + .search_reference(code.source(), code.s1("my_other_ent").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::Instantiation(my_ent, vec![]))); + assert!(options.contains(&CompletionItem::Instantiation(my_other_ent, vec![]))); + } +} diff --git a/vhdl_lang/src/completion/generic.rs b/vhdl_lang/src/completion/generic.rs new file mode 100644 index 0000000..fa4b1a2 --- /dev/null +++ b/vhdl_lang/src/completion/generic.rs @@ -0,0 +1,154 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::ast::search::{ + DeclarationItem, Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher, +}; +use crate::ast::ArchitectureBody; +use crate::completion::entity_instantiation::get_visible_entities_from_architecture; +use crate::completion::region::completion_items_from_region; +use crate::named_entity::{DesignEnt, Visibility}; +use crate::{CompletionItem, Design, HasTokenSpan, Position, Source, TokenAccess}; +use itertools::{chain, Itertools}; +use vhdl_lang::analysis::DesignRoot; + +pub(crate) fn generic_completions<'a>( + root: &'a DesignRoot, + cursor: Position, + source: &Source, +) -> Vec> { + let mut searcher = CompletionSearcher::new(cursor, root); + let _ = root.search_source(source, &mut searcher); + searcher.completions +} + +/// This is the most general-purpose completion provider. +/// This provider publishes all visible symbols reachable from some context. +/// This will, among other things produce many "non-regular" symbols, such as +/// operator symbols or specific characters. If possible, +/// this searcher should therefore be avoided in favor of a more specific completion provider. +struct CompletionSearcher<'a> { + root: &'a DesignRoot, + cursor: Position, + completions: Vec>, +} + +impl<'a> CompletionSearcher<'a> { + pub fn new(cursor: Position, design_root: &'a DesignRoot) -> CompletionSearcher<'a> { + CompletionSearcher { + root: design_root, + cursor, + completions: Vec::new(), + } + } +} + +impl<'a> CompletionSearcher<'a> { + /// Add entity instantiation completions that are visible from within an architecture body + fn add_entity_instantiations(&mut self, body: &ArchitectureBody) { + let Some(ent_id) = body.ident.decl.get() else { + return; + }; + let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { + return; + }; + self.completions + .extend(get_visible_entities_from_architecture(self.root, &ent)); + } +} + +impl<'a> Searcher for CompletionSearcher<'a> { + fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + let ent_id = match &decl.ast { + DeclarationItem::Entity(ent_decl) => { + if !ent_decl.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + ent_decl.ident.decl.get() + } + DeclarationItem::Architecture(body) => { + if !body.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + if body.statement_span().get_pos(ctx).contains(self.cursor) { + self.add_entity_instantiations(body); + } + body.ident.decl.get() + } + DeclarationItem::Package(package) => { + if !package.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + package.ident.decl.get() + } + DeclarationItem::PackageBody(package) => { + if !package.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + package.ident.decl.get() + } + DeclarationItem::Subprogram(subprogram) => { + if !subprogram.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + self.completions.extend( + subprogram + .declarations + .iter() + .flat_map(|decl| decl.item.declarations()) + .map(|id| CompletionItem::Simple(self.root.get_ent(id))), + ); + return NotFinished; + } + _ => return NotFinished, + }; + let Some(ent_id) = ent_id else { + return Finished(Found); + }; + let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { + return Finished(Found); + }; + self.completions + .extend(visible_entities_from(self.root, ent.kind())); + NotFinished + } +} + +fn visible_entities_from<'a>( + root: &'a DesignRoot, + design: &'a Design<'a>, +) -> Vec> { + use Design::*; + match design { + Entity(visibility, region) + | UninstPackage(visibility, region) + | Architecture(visibility, region, _) + | Package(visibility, region) + | PackageBody(visibility, region) => chain( + completion_items_from_region(root, region), + completion_items_from_visibility(root, visibility), + ) + .collect_vec(), + PackageInstance(region) | InterfacePackageInstance(region) | Context(region) => { + completion_items_from_region(root, region).collect_vec() + } + Configuration => vec![], + } +} + +fn completion_items_from_visibility<'a>( + root: &'a DesignRoot, + visibility: &'a Visibility<'a>, +) -> impl Iterator> { + visibility + .visible() + .unique() + .map(CompletionItem::Simple) + .chain( + visibility.all_in_region().flat_map(|visible_region| { + completion_items_from_region(root, visible_region.region()) + }), + ) +} diff --git a/vhdl_lang/src/completion/libraries.rs b/vhdl_lang/src/completion/libraries.rs new file mode 100644 index 0000000..745f703 --- /dev/null +++ b/vhdl_lang/src/completion/libraries.rs @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::CompletionItem; +use std::iter::once; + +/// Produces all available libraries. +pub(crate) fn list_all_libraries(root: &DesignRoot) -> Vec> { + root.libraries() + .map(|lib| CompletionItem::Simple(root.get_ent(lib.id()))) + .chain(once(CompletionItem::Work)) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{Code, LibraryBuilder}; + use crate::syntax::test::assert_eq_unordered; + use crate::{list_completion_options, CompletionItem}; + + #[test] + pub fn completing_libraries() { + let input = LibraryBuilder::new(); + let code = Code::new("library "); + let (root, _) = input.get_analyzed_root(); + let cursor = code.end(); + let options = list_completion_options(&root, code.source(), cursor); + assert_eq_unordered( + &options, + &[ + CompletionItem::Simple( + root.get_ent(root.get_lib(&root.symbol_utf8("std")).unwrap().id()), + ), + CompletionItem::Work, + ], + ) + } +} diff --git a/vhdl_lang/src/completion/map_aspect.rs b/vhdl_lang/src/completion/map_aspect.rs new file mode 100644 index 0000000..7498dcb --- /dev/null +++ b/vhdl_lang/src/completion/map_aspect.rs @@ -0,0 +1,353 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::ast::search::{ + DeclarationItem, Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher, +}; +use crate::ast::{ConcurrentStatement, MapAspect, ObjectClass}; +use crate::named_entity::{AsUnique, Region}; +use crate::{ + named_entity, AnyEntKind, CompletionItem, Design, EntityId, HasTokenSpan, Overloaded, Position, + Source, TokenAccess, +}; +use std::collections::HashSet; + +/// Produces completions for the left hand side of a map aspect, i.e., +/// `port map (` +pub(crate) fn completions_for_map_aspect<'a>( + root: &'a DesignRoot, + cursor: Position, + source: &Source, +) -> Vec> { + let mut searcher = MapAspectSearcher::new(root, cursor); + let _ = root.search_source(source, &mut searcher); + searcher.completions +} + +/// Searches completions for map aspects (VHDL port maps and generic maps). +/// Currently, this only means the formal part (i.e., the left hand side of a port or generic assignment) +/// but not the actual part. +struct MapAspectSearcher<'a> { + root: &'a DesignRoot, + cursor: Position, + completions: Vec>, +} + +impl<'a> MapAspectSearcher<'a> { + pub fn new(root: &'a DesignRoot, cursor: Position) -> MapAspectSearcher<'a> { + MapAspectSearcher { + root, + cursor, + completions: Vec::new(), + } + } + + /// Loads completion options for the given map aspect. + /// Returns `true`, when the cursor is inside the map aspect and the search should not continue. + /// Returns `false` otherwise + fn load_completions_for_map_aspect( + &mut self, + ent_ref: Option, + map: &MapAspect, + ctx: &dyn TokenAccess, + kind: MapAspectKind, + ) -> bool { + if !map.get_span(ctx).contains(self.cursor) { + return false; + } + let formals_in_map: HashSet = HashSet::from_iter(map.formals().flatten()); + if let Some(ent) = ent_ref { + let ids = match kind { + MapAspectKind::Port => extract_port_names(self.root, ent), + MapAspectKind::Generic => extract_generic_names(self.root, ent), + }; + self.completions.extend( + ids.into_iter() + .filter(|id| !formals_in_map.contains(id)) + .map(|id| CompletionItem::Formal(self.root.get_ent(id))), + ); + } + true + } +} + +impl<'a> Searcher for MapAspectSearcher<'a> { + /// Visit an instantiation statement extracting completions for ports or generics. + fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { + match &decl.ast { + DeclarationItem::ConcurrentStatement(stmt) => { + if let ConcurrentStatement::Instance(inst) = &stmt.statement.item { + if let Some(map) = &inst.generic_map { + if self.load_completions_for_map_aspect( + inst.entity_reference(), + map, + ctx, + MapAspectKind::Generic, + ) { + return Finished(Found); + } + } + if let Some(map) = &inst.port_map { + if self.load_completions_for_map_aspect( + inst.entity_reference(), + map, + ctx, + MapAspectKind::Port, + ) { + return Finished(Found); + } + } + } + } + DeclarationItem::PackageInstance(inst) => { + if let Some(map) = &inst.generic_map { + if self.load_completions_for_map_aspect( + inst.package_name.item.get_suffix_reference(), + map, + ctx, + MapAspectKind::Generic, + ) { + return Finished(Found); + } + } + } + _ => {} + } + NotFinished + } +} + +#[derive(Eq, PartialEq, Debug)] +enum MapAspectKind { + Port, + Generic, +} + +/// From this region, extracts those `AnyEntKind::Object`s where the class of the +/// object matches the specified class. +fn extract_objects_with_class(region: &Region<'_>, object_class: ObjectClass) -> Vec { + region + .entities + .values() + .filter_map(|ent| ent.as_unique()) + .filter_map(|ent| match &ent.kind { + AnyEntKind::Object(obj) if obj.class == object_class => Some(ent.id), + AnyEntKind::Overloaded(Overloaded::InterfaceSubprogram(_)) + if object_class == ObjectClass::Constant => + { + Some(ent.id) + } + AnyEntKind::Type(named_entity::Type::Interface) + if object_class == ObjectClass::Constant => + { + Some(ent.id) + } + _ => None, + }) + .collect() +} + +/// Extracts the name of ports or generics from an AST for an entity with a certain ID. +/// The entity can be an `Entity`, `Component` or `Package`. +/// +/// # Arguments +/// +/// * `object_class` - What to extract. `ObjectClass::Signal` extracts ports +/// while `ObjectClass::Constant` extracts constants. +fn extract_port_or_generic_names( + root: &DesignRoot, + id: EntityId, + object_class: ObjectClass, +) -> Vec { + let cmp_ent = root.get_ent(id); + match cmp_ent.kind() { + AnyEntKind::Component(region) => extract_objects_with_class(region, object_class), + AnyEntKind::Design(Design::Entity(_, region)) => { + extract_objects_with_class(region, object_class) + } + AnyEntKind::Design(Design::UninstPackage(_, region)) => { + extract_objects_with_class(region, object_class) + } + _ => vec![], + } +} + +pub fn extract_port_names(root: &DesignRoot, id: EntityId) -> Vec { + extract_port_or_generic_names(root, id, ObjectClass::Signal) +} + +pub fn extract_generic_names(root: &DesignRoot, id: EntityId) -> Vec { + extract_port_or_generic_names(root, id, ObjectClass::Constant) +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; + use crate::{list_completion_options, CompletionItem}; + + #[test] + pub fn complete_component_instantiation_map() { + let mut input = LibraryBuilder::new(); + let code = input.code( + "libname", + "\ + entity my_ent is + end entity my_ent; + + architecture arch of my_ent is + component comp is + generic ( + A: natural := 5; + B: integer + ); + port ( + clk : in bit; + rst : in bit; + dout : out bit + ); + end component comp; + signal clk, rst: bit; + begin + comp_inst: comp + generic map ( + A => 2 + ) + port map ( + clk => clk + ); + end arch; + ", + ); + let (root, _) = input.get_analyzed_root(); + let cursor = code.s1("generic map (").pos().end(); + let options = list_completion_options(&root, code.source(), cursor); + let ent = root + .search_reference(code.source(), code.s1("B").start()) + .unwrap(); + assert_eq!(options, vec![CompletionItem::Formal(ent)]); + + let rst = root + .search_reference(code.source(), code.s1("rst").start()) + .unwrap(); + + let dout = root + .search_reference(code.source(), code.s1("dout").start()) + .unwrap(); + + let clk_signal = root + .search_reference( + code.source(), + code.s1("signal clk, rst: bit;").s1("clk").start(), + ) + .unwrap(); + + let rst_signal = root + .search_reference( + code.source(), + code.s1("signal clk, rst: bit;").s1("rst").start(), + ) + .unwrap(); + + let cursor = code.s1("port map (").pos().end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Formal(rst))); + assert!(options.contains(&CompletionItem::Formal(dout))); + assert_eq!(options.len(), 2); + let cursor = code + .s1("port map ( + clk =>") + .pos() + .end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Simple(clk_signal))); + assert!(options.contains(&CompletionItem::Simple(rst_signal))); + + let cursor = code + .s1("port map ( + clk => c") + .pos() + .end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Simple(clk_signal))); + assert!(options.contains(&CompletionItem::Simple(rst_signal))); + } + + #[test] + pub fn complete_in_generic_map() { + let mut input = LibraryBuilder::new(); + let code = input.code( + "libname", + "\ + package my_pkg is + generic ( + function foo(x: Integer) return bit; + function bar(y: Integer) return boolean; + type T; + x: natural + ); + end my_pkg; + + use work.my_pkg.all ; + package my_pkg_inst is new work.my_pkg + generic map ( + foo => foo + );", + ); + let (root, _) = input.get_analyzed_root(); + let bar_func = root + .search_reference(code.source(), code.s1("bar").start()) + .unwrap(); + let x = root + .search_reference(code.source(), code.s1("x: natural").s1("x").start()) + .unwrap(); + let t = root + .search_reference(code.source(), code.s1("type T").s1("T").start()) + .unwrap(); + let cursor = code.s1("generic map (").pos().end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Formal(bar_func))); + assert!(options.contains(&CompletionItem::Formal(x))); + assert!(options.contains(&CompletionItem::Formal(t))); + pretty_assertions::assert_eq!(options.len(), 3); + } + + #[test] + pub fn completes_signals_and_ports() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +entity my_ent is + port ( + foo : in bit + ); +end my_ent; + +architecture arch of my_ent is + signal bar : natural; + type foobaz is array(natural range <>) of bit; +begin +end arch; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag); + let cursor = code.s1("begin").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let ent1 = root + .search_reference(code.source(), code.s1("foo").start()) + .unwrap(); + + let ent2 = root + .search_reference(code.source(), code.s1("bar").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::Simple(ent1))); + assert!(options.contains(&CompletionItem::Simple(ent2))); + } +} diff --git a/vhdl_lang/src/completion/region.rs b/vhdl_lang/src/completion/region.rs new file mode 100644 index 0000000..5f2380e --- /dev/null +++ b/vhdl_lang/src/completion/region.rs @@ -0,0 +1,47 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com + +use crate::analysis::DesignRoot; +use crate::completion::entity_instantiation::get_architectures_for_entity; +use crate::named_entity::{AsUnique, NamedEntities, Region}; +use crate::{AnyEntKind, CompletionItem, Design}; +use vhdl_lang::EntRef; + +pub(crate) fn completion_items_from_region<'a>( + root: &'a DesignRoot, + region: &'a Region<'a>, +) -> impl Iterator> { + region + .entities + .values() + .map(|entities| named_entities_to_completion_item(root, entities)) +} + +fn named_entities_to_completion_item<'a>( + root: &'a DesignRoot, + named_entities: &'a NamedEntities<'a>, +) -> CompletionItem<'a> { + match named_entities { + NamedEntities::Single(ent) => any_ent_to_completion_item(ent, root), + NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() { + None => CompletionItem::Overloaded(overloaded.designator().clone(), overloaded.len()), + Some(ent) => CompletionItem::Simple(ent), + }, + } +} + +pub(crate) fn any_ent_to_completion_item<'a>( + ent: EntRef<'a>, + root: &'a DesignRoot, +) -> CompletionItem<'a> { + match ent.kind() { + AnyEntKind::Design(Design::Entity(..)) | AnyEntKind::Component(_) => { + let architectures = get_architectures_for_entity(ent, root); + CompletionItem::Instantiation(ent, architectures) + } + _ => CompletionItem::Simple(ent), + } +} diff --git a/vhdl_lang/src/completion/selected.rs b/vhdl_lang/src/completion/selected.rs new file mode 100644 index 0000000..63ba410 --- /dev/null +++ b/vhdl_lang/src/completion/selected.rs @@ -0,0 +1,204 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::completion::region::completion_items_from_region; +use crate::data::Symbol; +use crate::syntax::Kind::All; +use crate::{named_entity, CompletionItem, EntRef, HasEntityId}; +use std::iter::once; + +/// Produces completions for a selected name, i.e., +/// `foo.` +/// The provided `ent` is the entity directly before the dot, i.e., +/// `foo` in the example above. +pub(crate) fn completions_for_selected_name<'b>( + root: &'b DesignRoot, + ent: EntRef<'b>, +) -> Vec> { + use crate::named_entity::AnyEntKind::*; + match ent.kind() { + Object(object) => completions_for_type(root, object.subtype.type_mark().kind()), + Design(design) => completions_for_design(root, design), + Library => ent + .library_name() + .map(|sym| list_primaries_for_lib(root, sym)) + .unwrap_or_default(), + _ => vec![], + } +} + +/// Returns completions applicable when calling `foo.` where `foo` is amn object of some type. +fn completions_for_type<'a>( + root: &'a DesignRoot, + typ: &'a named_entity::Type<'a>, +) -> Vec> { + use crate::named_entity::Type::*; + match typ { + Record(record_region) => record_region + .iter() + .map(|item| CompletionItem::Simple(item.ent)) + .collect(), + Alias(type_ent) => completions_for_type(root, type_ent.kind()), + Access(subtype) => { + let mut completions = completions_for_type(root, subtype.type_mark().kind()); + completions.push(CompletionItem::Keyword(All)); + completions + } + Protected(region, _) => completion_items_from_region(root, region).collect(), + _ => vec![], + } +} + +/// Returns completions applicable when calling `foo.` where `foo` is some design +/// (i.e. entity or package). +fn completions_for_design<'a>( + root: &'a DesignRoot, + design: &'a crate::Design<'a>, +) -> Vec> { + use crate::named_entity::Design::*; + match design { + Package(_, region) | PackageInstance(region) | InterfacePackageInstance(region) => { + completion_items_from_region(root, region) + .chain(once(CompletionItem::Keyword(All))) + .collect() + } + _ => vec![], + } +} + +/// List the name of all primary units for a given library. +/// If the library is non-resolvable, list an empty vector +fn list_primaries_for_lib<'a>(root: &'a DesignRoot, lib: &Symbol) -> Vec> { + let Some(lib) = root.get_lib(lib) else { + return vec![]; + }; + lib.primary_units() + .filter_map(|it| it.unit.get().and_then(|unit| unit.ent_id())) + .map(|id| CompletionItem::Simple(root.get_ent(id))) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{assert_eq_unordered, LibraryBuilder}; + use crate::ast::Designator; + use crate::syntax::Kind::All; + use crate::{list_completion_options, CompletionItem}; + + #[test] + pub fn completes_selected_names() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +package foo is + type my_record is record + abc: bit; + def: bit; + end record; + + constant y: my_record := ('1', '1'); + constant z: bit := y. +end foo; + ", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("y.").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let ent1 = root + .search_reference(code.source(), code.s1("abc").start()) + .unwrap(); + + let ent2 = root + .search_reference(code.source(), code.s1("def").start()) + .unwrap(); + + assert_eq_unordered( + &options, + &[CompletionItem::Simple(ent1), CompletionItem::Simple(ent2)], + ) + } + + #[test] + pub fn completing_primaries() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +use std. + +-- Need this so that the 'use std.' is associated to a context and can be analyzed correctly +package x is +end package x; +", + ); + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("use std.").end(); + let options = list_completion_options(&root, code.source(), cursor); + assert_eq_unordered( + &options, + &[ + CompletionItem::Simple(root.find_textio_pkg()), + CompletionItem::Simple(root.find_standard_pkg()), + CompletionItem::Simple(root.find_env_pkg()), + ], + ); + + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +use std.t + +-- Need this so that the 'use std.' is associated to a context and can be analyzed correctly +package x is +end package x; +", + ); + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("use std.t").end(); + let options = list_completion_options(&root, code.source(), cursor); + // Note that the filtering only happens at client side + assert_eq_unordered( + &options, + &[ + CompletionItem::Simple(root.find_textio_pkg()), + CompletionItem::Simple(root.find_standard_pkg()), + CompletionItem::Simple(root.find_env_pkg()), + ], + ); + } + + #[test] + pub fn completing_declarations() { + let mut input = LibraryBuilder::new(); + let code = input.code( + "libname", + "\ +use std.env. + +-- Need this so that the 'use std.env.' is associated to a context and can be analyzed correctly +package x is +end package x; +", + ); + let (root, _) = input.get_analyzed_root(); + let cursor = code.s1("use std.env.").end(); + let options = list_completion_options(&root, code.source(), cursor); + + assert_eq_unordered( + &options, + &[ + CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("stop")), 2), + CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("finish")), 2), + CompletionItem::Simple(root.find_env_symbol("resolution_limit")), + CompletionItem::Keyword(All), + ], + ); + } +} diff --git a/vhdl_lang/src/completion/tokenizer.rs b/vhdl_lang/src/completion/tokenizer.rs new file mode 100644 index 0000000..1d004a5 --- /dev/null +++ b/vhdl_lang/src/completion/tokenizer.rs @@ -0,0 +1,111 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::data::ContentReader; +use crate::syntax::{Symbols, Tokenizer}; +use crate::{Position, Source, Token}; + +/// Tokenizes `source` up to `cursor` but no further. The last token returned is the token +/// where the cursor currently resides or the token right before the cursor. +/// +/// Examples: +/// +/// input = "use ieee.std_logic_1|164.a" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} +/// +/// input = "use ieee.std_logic_1164|.a" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} +/// +/// input = "use ieee.std_logic_1164.|a" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT} +/// input = "use ieee.std_logic_1164.a|" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT, a} +/// +/// On error, or if the source is empty, returns an empty vector. +pub(crate) fn tokenize_input(symbols: &Symbols, source: &Source, cursor: Position) -> Vec { + let contents = source.contents(); + let mut tokenizer = Tokenizer::new(symbols, source, ContentReader::new(&contents)); + let mut tokens = Vec::new(); + loop { + match tokenizer.pop() { + Ok(Some(token)) => { + if token.pos.start() >= cursor { + break; + } + tokens.push(token); + } + Ok(None) => break, + Err(_) => return vec![], + } + } + tokens +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::test::Code; + use crate::syntax::Kind::*; + use assert_matches::assert_matches; + + macro_rules! kind { + ($kind: pat) => { + crate::syntax::Token { kind: $kind, .. } + }; + } + + #[test] + fn tokenizing_an_empty_input() { + let input = Code::new(""); + let tokens = tokenize_input(&input.symbols, input.source(), Position::new(0, 0)); + assert_eq!(tokens.len(), 0); + } + + #[test] + fn tokenizing_stops_at_the_cursors_position() { + let input = Code::new("use ieee.std_logic_1164.all"); + let mut cursor = input.s1("std_logic_11").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] + ); + cursor = input.s1("std_logic_1164").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] + ); + cursor = input.s1("std_logic_1164.").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [ + kind!(Use), + kind!(Identifier), + kind!(Dot), + kind!(Identifier), + kind!(Dot) + ] + ); + cursor = input.s1("std_logic_1164.all").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [ + kind!(Use), + kind!(Identifier), + kind!(Dot), + kind!(Identifier), + kind!(Dot), + kind!(All) + ] + ); + } +} diff --git a/vhdl_lang/src/config.rs b/vhdl_lang/src/config.rs new file mode 100644 index 0000000..5e793ee --- /dev/null +++ b/vhdl_lang/src/config.rs @@ -0,0 +1,800 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +//! Configuration of the design hierarchy and other settings + +use std::env; +use std::fs::File; +use std::io; +use std::io::prelude::*; +use std::path::Path; + +use fnv::FnvHashMap; +use itertools::Itertools; +use subst::VariableMap; +use toml::{Table, Value}; + +use crate::data::error_codes::ErrorCode; +use crate::data::*; +use crate::standard::VHDLStandard; + +#[derive(Clone, PartialEq, Eq, Default, Debug)] +pub struct Config { + // A map from library name to file name + libraries: FnvHashMap, + standard: VHDLStandard, + // Defines the severity that diagnostics are displayed with + severities: SeverityMap, +} + +#[derive(Clone, PartialEq, Eq, Default, Debug)] +pub struct LibraryConfig { + name: String, + patterns: Vec, + pub(crate) is_third_party: bool, +} + +impl LibraryConfig { + /// Return a vector of file names + /// Only include files that exists + /// Files that do not exist produce a warning message + pub fn file_names(&self, messages: &mut dyn MessageHandler) -> Vec { + let mut result = Vec::new(); + for pattern in self.patterns.iter() { + let stripped_pattern = if cfg!(windows) { + pattern.strip_prefix("\\\\?\\").unwrap_or(pattern.as_str()) + } else { + pattern.as_str() + }; + + if is_literal(stripped_pattern) { + let file_path = PathBuf::from(pattern); + + if file_path.exists() { + result.push(file_path); + } else { + messages.push(Message::warning(format! {"File {pattern} does not exist"})); + } + } else { + match glob::glob(stripped_pattern) { + Ok(paths) => { + let mut empty_pattern = true; + + for file_path_or_error in paths { + empty_pattern = false; + match file_path_or_error { + Ok(file_path) => { + result.push(file_path); + } + Err(err) => { + messages.push(Message::error(err.to_string())); + } + } + } + + if empty_pattern { + messages.push(Message::warning(format!( + "Pattern '{stripped_pattern}' did not match any file" + ))); + } + } + Err(err) => { + messages.push(Message::error(format!("Invalid pattern '{pattern}' {err}"))); + } + } + } + } + // Remove duplicate file names from the result + result.into_iter().unique().collect() + } + + /// Returns the name of the library + pub fn name(&self) -> &str { + self.name.as_str() + } +} + +impl Config { + pub fn from_str(string: &str, parent: &Path) -> Result { + let config = string.parse::().map_err(|err| err.to_string())?; + let mut libraries = FnvHashMap::default(); + + let standard = if let Some(std) = config.get("standard") { + let std_str = std.as_str().ok_or("standard must be a string")?; + VHDLStandard::try_from(std_str) + .map_err(|_| format!("Unsupported standard '{std_str}'"))? + } else { + VHDLStandard::default() + }; + + let libs = config + .get("libraries") + .ok_or("missing field libraries")? + .as_table() + .ok_or("libraries must be a table")?; + + for (name, lib) in libs.iter() { + if name.to_lowercase() == "work" { + return Err(format!( + "The '{}' library is not a valid library.\nHint: To use a library that contains all files, use a common name for all libraries, i.e., 'defaultlib'", + name + )); + } + + let file_arr = lib + .get("files") + .ok_or_else(|| format!("missing field files for library {name}"))? + .as_array() + .ok_or_else(|| format!("files for library {name} is not array"))?; + + let mut patterns = Vec::new(); + for file in file_arr.iter() { + let file = file + .as_str() + .ok_or_else(|| format!("not a string {file}"))?; + + let file = substitute_environment_variables(file, &subst::Env)?; + + let path = parent.join(file); + let path = path + .to_str() + .ok_or_else(|| format!("Could not convert {path:?} to string"))? + .to_owned(); + patterns.push(path); + } + + let mut is_third_party = false; + if let Some(opt) = lib.get("is_third_party") { + if let Some(opt) = opt.as_bool() { + is_third_party = opt; + } else { + return Err(format!( + "Expected is_third_party to be boolean for library {name}" + )); + } + } + + libraries.insert( + name.to_owned(), + LibraryConfig { + name: name.to_owned(), + patterns, + is_third_party, + }, + ); + } + + let severities = if let Some(lint) = config.get("lint") { + Self::read_severity_overwrites(lint.as_table().ok_or("lint must be a table")?)? + } else { + SeverityMap::default() + }; + + Ok(Config { + libraries, + severities, + standard, + }) + } + + fn read_severity_overwrites(severity_overwrites: &Table) -> Result { + let mut severities = SeverityMap::default(); + + for (name, severity) in severity_overwrites { + let error_code = ErrorCode::try_from(name.as_str()) + .map_err(|_| format!("'{name}' is not a valid error code"))?; + match severity { + Value::String(severity) => { + let severity = Severity::try_from(severity.as_str()) + .map_err(|_| format!("'{severity}' is not a valid severity level"))?; + severities[error_code] = Some(severity); + } + Value::Boolean(should_show) => { + if !should_show { + severities[error_code] = None + } + } + _ => return Err("severity must be a string or boolean".to_string()), + } + } + Ok(severities) + } + + pub fn read_file_path(file_name: &Path) -> io::Result { + let mut file = File::open(file_name)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let parent = file_name.parent().unwrap(); + + Config::from_str(&contents, parent).map_err(|msg| io::Error::new(io::ErrorKind::Other, msg)) + } + + pub fn get_library(&self, name: &str) -> Option<&LibraryConfig> { + self.libraries.get(name) + } + + pub fn iter_libraries(&self) -> impl Iterator { + self.libraries.values() + } + + /// Append another config to self + /// + /// In case of conflict the appended config takes precedence + pub fn append(&mut self, config: &Config, messages: &mut dyn MessageHandler) { + self.standard = config.standard; + for library in config.iter_libraries() { + if let Some(parent_library) = self.libraries.get_mut(&library.name) { + *parent_library = library.clone(); + + messages.push(Message::warning(format!( + "Re-defined library {}", + &library.name + ))); + } else { + self.libraries.insert(library.name.clone(), library.clone()); + } + } + self.severities = config.severities; + } + + /// Load configuration file from installation folder + fn load_installed_config( + &mut self, + messages: &mut dyn MessageHandler, + location: Option, + ) { + if let Some(location) = location { + self.load_config(&PathBuf::from(location), "Installation", messages); + return; + } + let search_paths = [ + "../vhdl_libraries", + "../../vhdl_libraries", + "/usr/lib/rust_hdl/vhdl_libraries", + "/usr/local/lib/rust_hdl/vhdl_libraries", + "../share/vhdl_libraries", + ]; + + for dir in search_paths.into_iter() { + let mut file_name = PathBuf::from(dir); + // Expand a relative path + if !file_name.is_absolute() { + let exe_path = env::current_exe().expect("Executable path needed"); + let exe_folder = exe_path.parent().expect("Executable folder must exist"); + file_name = exe_folder.join(file_name) + } + file_name.push("vhdl_ls.toml"); + if file_name.exists() { + self.load_config(&file_name, "Installation", messages); + return; + } + } + + // Panic if we did not yet find the installed libraries + panic!( + "Couldn't find installed libraries at {}.", + search_paths.join(", ") + ); + } + + /// Load configuration file from home folder + fn load_home_config(&mut self, messages: &mut dyn MessageHandler) { + if let Some(home_dir) = dirs::home_dir() { + let file_name = home_dir.join(".vhdl_ls.toml"); + + if !file_name.exists() { + return; + } + + self.load_config(&file_name, "HOME folder", messages); + } + } + + /// Load configuration file from environment + fn load_env_config(&mut self, env_name: &str, messages: &mut dyn MessageHandler) { + if let Some(file_name) = std::env::var_os(env_name) { + self.load_config(Path::new(&file_name), env_name, messages); + }; + } + + /// Load and append configuration file + fn load_config(&mut self, file_name: &Path, desc: &str, messages: &mut dyn MessageHandler) { + match Config::read_file_path(Path::new(&file_name)) { + Ok(env_config) => { + messages.push(Message::log(format!( + "Loaded {} configuration file: {}", + desc, + file_name.to_string_lossy() + ))); + + self.append(&env_config, messages); + } + Err(ref err) => { + messages.push(Message::error(format!( + "Error while loading {desc} configuration file: {err} " + ))); + } + } + } + + /// Load all external configuration + /// If the `standard_libraries_path` is given, it must point to a valid + /// `vhdl_ls.toml` file, which will be used as source for the standard libraries + /// i.e., `std` or `ieee`. + /// If this path is `None`, a set of standard search paths will be queried for the location + /// of this file. + pub fn load_external_config( + &mut self, + messages: &mut dyn MessageHandler, + standard_libraries_path: Option, + ) { + self.load_installed_config(messages, standard_libraries_path); + self.load_home_config(messages); + self.load_env_config("VHDL_LS_CONFIG", messages); + } + + pub fn severities(&self) -> &SeverityMap { + &self.severities + } + + /// The VHDL standard to use if no more specific config is present. + /// By default, VHDL 2008 is assumed + pub fn standard(&self) -> VHDLStandard { + self.standard + } +} + +fn substitute_environment_variables<'a, M>(s: &str, map: &'a M) -> Result +where + M: VariableMap<'a> + ?Sized, + M::Value: AsRef, +{ + if cfg!(windows) { + substitute_variables_windows(s, map) + } else { + subst::substitute(s, map).map_err(|err| err.to_string()) + } +} + +fn substitute_variables_windows<'a, M>(s: &str, map: &'a M) -> Result +where + M: VariableMap<'a> + ?Sized, + M::Value: AsRef, +{ + let mut output: Vec = Vec::with_capacity(s.len()); + let mut var_buf: Vec = Vec::new(); + + let mut var_found = false; + + for ch in s.chars() { + if ch == '%' { + if var_found { + let var_name = String::from_iter(var_buf); + var_buf = Vec::new(); + match map.get(&var_name) { + None => { + return Err(format!("Variable '{var_name}' not found")); + } + Some(value) => { + output.extend(value.as_ref().chars()); + } + } + } + var_found = !var_found; + } else if !var_found { + output.push(ch); + } else { + var_buf.push(ch) + } + } + + if var_found { + Err("Unterminated variable".into()) + } else { + Ok(String::from_iter(output)) + } +} + +/// Returns true if the pattern is a plain file name and not a glob pattern +fn is_literal(pattern: &str) -> bool { + !pattern.chars().any(|chr| matches!(&chr, '?' | '*' | '[')) +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use pretty_assertions::assert_eq; + + use super::*; + + /// Utility function to create an empty file in parent folder + fn touch(parent: &Path, file_name: &str) -> PathBuf { + let path = parent.join(file_name); + File::create(&path).expect("Assume file can be created"); + path + } + + fn abspath(path: &Path) -> PathBuf { + dunce::canonicalize(path).unwrap() + } + + fn abspaths(paths: &[PathBuf]) -> Vec { + paths.iter().map(|path| abspath(path)).collect() + } + + fn assert_files_eq(got: &[PathBuf], expected: &[PathBuf]) { + assert_eq!(abspaths(got), abspaths(expected)); + } + + #[test] + fn test_is_literal() { + assert!(is_literal("file.vhd")); + assert!(!is_literal("file*.vhd")); + assert!(!is_literal("file?.vhd")); + assert!(!is_literal("file[ab].vhd")); + } + + #[test] + fn config_from_str() { + let tempdir = tempfile::tempdir().unwrap(); + let parent = tempdir.path(); + + let tempdir2 = tempfile::tempdir().unwrap(); + let absolute_path = abspath(tempdir2.path()); + let absolute_vhd = touch(&absolute_path, "absolute.vhd"); + + let config = Config::from_str( + &format!( + " +[libraries] +lib2.files = [ + 'pkg2.vhd', + '{}' +] +lib1.files = [ + 'pkg1.vhd', + 'tb_ent.vhd' +] + +[lint] +unused = 'error' +duplicate = false +", + absolute_vhd.to_str().unwrap() + ), + parent, + ) + .unwrap(); + let mut libraries: Vec<&str> = config.iter_libraries().map(|lib| lib.name()).collect(); + libraries.sort_unstable(); + assert_eq!(libraries, &["lib1", "lib2"]); + + let lib1 = config.get_library("lib1").unwrap(); + let lib2 = config.get_library("lib2").unwrap(); + + let pkg1_path = touch(parent, "pkg1.vhd"); + let pkg2_path = touch(parent, "pkg2.vhd"); + let tb_ent_path = touch(parent, "tb_ent.vhd"); + + let mut messages = vec![]; + assert_files_eq(&lib1.file_names(&mut messages), &[pkg1_path, tb_ent_path]); + assert_files_eq(&lib2.file_names(&mut messages), &[pkg2_path, absolute_vhd]); + assert_eq!(messages, vec![]); + + let mut expected_map = SeverityMap::default(); + expected_map[ErrorCode::Unused] = Some(Severity::Error); + expected_map[ErrorCode::Duplicate] = None; + assert_eq!(config.severities, expected_map) + } + + #[test] + fn test_append_config() { + let parent0 = Path::new("parent_folder0"); + let config0 = Config::from_str( + " +[libraries] +lib1.files = [ + 'pkg1.vhd', +] +lib2.files = [ + 'pkg2.vhd' +] +", + parent0, + ) + .unwrap(); + + let parent1 = Path::new("parent_folder1"); + let config1 = Config::from_str( + " +[libraries] +lib2.files = [ + 'ent.vhd' +] +lib3.files = [ + 'pkg3.vhd', +] +", + parent1, + ) + .unwrap(); + + let expected_parent = Path::new(""); + let expected_config = Config::from_str( + &format!( + " +[libraries] +lib1.files = [ + '{pkg1}', +] +lib2.files = [ + '{ent}' +] +lib3.files = [ + '{pkg3}', +] +", + pkg1 = parent0.join("pkg1.vhd").to_str().unwrap(), + ent = parent1.join("ent.vhd").to_str().unwrap(), + pkg3 = parent1.join("pkg3.vhd").to_str().unwrap() + ), + expected_parent, + ) + .unwrap(); + + let mut merged_config = config0; + merged_config.append(&config1, &mut Vec::new()); + assert_eq!(merged_config, expected_config); + } + + #[test] + fn test_warning_on_missing_file() { + let parent = Path::new("parent_folder"); + let config = Config::from_str( + " +[libraries] +lib.files = [ + 'missing.vhd' +] +", + parent, + ) + .unwrap(); + + let mut messages = vec![]; + let file_names = config.get_library("lib").unwrap().file_names(&mut messages); + assert_files_eq(&file_names, &[]); + assert_eq!( + messages, + vec![Message::warning(format!( + "File {} does not exist", + parent.join("missing.vhd").to_str().unwrap() + ))] + ); + } + + #[test] + fn test_file_wildcard_pattern() { + let tempdir = tempfile::tempdir().unwrap(); + let parent = tempdir.path(); + let config = Config::from_str( + " +[libraries] +lib.files = [ + '*.vhd' +] +", + parent, + ) + .unwrap(); + + let file1 = touch(parent, "file1.vhd"); + let file2 = touch(parent, "file2.vhd"); + + let mut messages = vec![]; + let file_names = config.get_library("lib").unwrap().file_names(&mut messages); + assert_files_eq(&file_names, &[file1, file2]); + assert_eq!(messages, vec![]); + } + + #[test] + fn test_file_wildcard_pattern_removes_duplicates() { + let tempdir = tempfile::tempdir().unwrap(); + let parent = tempdir.path(); + let config = Config::from_str( + " +[libraries] +lib.files = [ + '*.vhd', + 'file*.vhd' +] +", + parent, + ) + .unwrap(); + + let file1 = touch(parent, "file1.vhd"); + let file2 = touch(parent, "file2.vhd"); + + let mut messages = vec![]; + let file_names = config.get_library("lib").unwrap().file_names(&mut messages); + assert_files_eq(&file_names, &[file1, file2]); + assert_eq!(messages, vec![]); + } + + #[test] + fn test_warning_on_emtpy_glob_pattern() { + let parent = Path::new("parent_folder"); + let config = Config::from_str( + " +[libraries] +lib.files = [ + 'missing*.vhd' +] +", + parent, + ) + .unwrap(); + + let mut messages = vec![]; + let file_names = config.get_library("lib").unwrap().file_names(&mut messages); + assert_files_eq(&file_names, &[]); + assert_eq!( + messages, + vec![Message::warning(format!( + "Pattern '{}' did not match any file", + parent.join("missing*.vhd").to_str().unwrap() + ))] + ); + } + + #[test] + fn the_work_library_is_an_illegal_library() { + let parent = Path::new("parent_folder"); + let config = Config::from_str( + " +[libraries] +work.files = [ + 'a.vhd', 'b.vhd' +] +", + parent, + ); + assert_eq!(config.expect_err("Expected erroneous config"), "The 'work' library is not a valid library.\nHint: To use a library that contains all files, use a common name for all libraries, i.e., 'defaultlib'") + } + + #[test] + #[cfg(unix)] + fn substitute() { + let mut map = HashMap::new(); + map.insert("A".to_owned(), "a".to_owned()); + map.insert("ABCD".to_owned(), "abcd".to_owned()); + map.insert("A_0".to_owned(), "a0".to_owned()); + map.insert("_".to_owned(), "u".to_owned()); + map.insert("PATH".to_owned(), "some/path".to_owned()); + + // simple pattern tests + assert_eq!( + Ok("test".to_owned()), + substitute_environment_variables("test", &map) + ); + assert_eq!( + Ok("a".to_owned()), + substitute_environment_variables("$A", &map) + ); + assert_eq!( + Ok("abcd".to_owned()), + substitute_environment_variables("$ABCD", &map) + ); + assert_eq!( + Ok("a0".to_owned()), + substitute_environment_variables("$A_0", &map) + ); + assert_eq!( + Ok("u".to_owned()), + substitute_environment_variables("$_", &map) + ); + assert_eq!( + Ok("some/path".to_owned()), + substitute_environment_variables("$PATH", &map) + ); + + // embedded in longer string + assert_eq!( + Ok("test/a/test".to_owned()), + substitute_environment_variables("test/$A/test", &map) + ); + assert_eq!( + Ok("test/a".to_owned()), + substitute_environment_variables("test/$A", &map) + ); + assert_eq!( + Ok("a/test".to_owned()), + substitute_environment_variables("$A/test", &map) + ); + assert_eq!( + Ok("test/some/path/test".to_owned()), + substitute_environment_variables("test/$PATH/test", &map) + ); + + // error cases + assert!(substitute_environment_variables("$not_present", &map).is_err()); + assert!(substitute_environment_variables("$not_unicode", &map).is_err()); + } + + #[test] + fn windows_variable_names() { + let mut map = HashMap::new(); + map.insert("A".to_owned(), "a".to_owned()); + map.insert("ABCD".to_owned(), "abcd".to_owned()); + map.insert("A_0".to_owned(), "a0".to_owned()); + map.insert("_".to_owned(), "u".to_owned()); + map.insert("PATH".to_owned(), r#"some\path"#.to_owned()); + + assert_eq!(Ok("".to_owned()), substitute_variables_windows("", &map)); + assert_eq!( + Ok("test".to_owned()), + substitute_variables_windows("test", &map) + ); + assert_eq!( + Ok("a".to_owned()), + substitute_variables_windows("%A%", &map) + ); + assert_eq!( + Ok("abcd".to_owned()), + substitute_variables_windows("%ABCD%", &map) + ); + assert_eq!( + Ok("a0".to_owned()), + substitute_variables_windows("%A_0%", &map) + ); + assert_eq!( + Ok("u".to_owned()), + substitute_variables_windows("%_%", &map) + ); + assert_eq!( + Ok(r#"some\path"#.to_owned()), + substitute_variables_windows("%PATH%", &map) + ); + + // embedded in longer string + assert_eq!( + Ok(r#"test\a\test"#.to_owned()), + substitute_variables_windows(r#"test\%A%\test"#, &map) + ); + assert_eq!( + Ok(r#"test\a"#.to_owned()), + substitute_variables_windows(r#"test\%A%"#, &map) + ); + assert_eq!( + Ok(r#"a\test"#.to_owned()), + substitute_variables_windows(r#"%A%\test"#, &map) + ); + assert_eq!( + Ok(r#"C:\test\some\path\test"#.to_owned()), + substitute_variables_windows(r#"C:\test\%PATH%\test"#, &map) + ); + + // error cases + assert_eq!( + substitute_variables_windows("%not_present%", &map), + Err("Variable 'not_present' not found".into()) + ); + assert!(substitute_variables_windows("%not_unicode%", &map).is_err()); + } + + // Issue #278 + #[test] + #[cfg(windows)] + fn substitute_ok_windows_paths() { + let map: HashMap = HashMap::default(); + let str = r#"\\networklocation\cad$\apps\xilinx_vitis\Vivado_2020.2\Vivado\2020.2\data\vhdl\src\unisims\unisim_VCOMP.vhd"#; + let res = substitute_environment_variables(str, &map); + assert_eq!(res, Ok(str.to_owned())); + } +} diff --git a/vhdl_lang/src/data.rs b/vhdl_lang/src/data.rs new file mode 100644 index 0000000..1b8e6cd --- /dev/null +++ b/vhdl_lang/src/data.rs @@ -0,0 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +mod contents; +mod diagnostic; +pub mod error_codes; +mod latin_1; +mod message; +mod source; +mod symbol_table; + +pub use contents::*; +pub use diagnostic::*; +pub use error_codes::*; +pub use latin_1::*; +pub use message::*; +pub use source::*; +pub use symbol_table::*; diff --git a/vhdl_lang/src/data/contents.rs b/vhdl_lang/src/data/contents.rs new file mode 100644 index 0000000..451e219 --- /dev/null +++ b/vhdl_lang/src/data/contents.rs @@ -0,0 +1,632 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use super::latin_1::{char_to_latin1, Latin1String, Utf8ToLatin1Error}; +use super::source::{Position, Range}; +use std::fs::File; +use std::io; +use std::io::prelude::Read; +use std::path::Path; + +pub struct Contents { + lines: Vec, +} + +impl Contents { + pub fn from_latin1_file(file_name: &Path) -> io::Result { + let mut file = File::open(file_name)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + Ok(Contents::from_str( + &Latin1String::from_vec(bytes).to_string(), + )) + } + + pub fn from_str(code: &str) -> Contents { + Contents { + lines: split_lines(code), + } + } + + pub fn start(&self) -> Position { + Position { + line: 0, + character: 0, + } + } + + pub fn end(&self) -> Position { + let line = self.num_lines().saturating_sub(1) as u32; + let character = self + .lines + .last() + .map(|line| line.chars().map(|chr| chr.len_utf16()).sum()) + .unwrap_or(0) as u32; + Position { line, character } + } + + #[cfg(test)] + pub fn range(&self) -> Range { + Range::new(self.start(), self.end()) + } + + #[cfg(test)] + pub fn crop(&self, range: Range) -> Contents { + let mut reader = ContentReader::new(self); + reader.seek_pos(range.start); + + let mut result = String::new(); + while reader.pos() < range.end { + if let Some(chr) = reader.pop_char() { + result.push(chr); + } + } + + Contents { + lines: split_lines(&result), + } + } + + pub fn num_lines(&self) -> usize { + self.lines.len() + } + + pub fn get_line(&self, lineno: usize) -> Option<&str> { + self.lines.get(lineno).map(|string| string.as_str()) + } + + pub fn change(&mut self, range: &Range, content: &str) { + if self.lines.is_empty() { + self.lines = split_lines(content); + return; + } + + let Range { start, end } = range; + + let start_char = start.character as usize; + let end_char = end.character as usize; + let start_line = start.line as usize; + let end_line = end.line as usize; + let mut merged_content = String::new(); + + if let Some(line) = self.lines.get(start_line) { + let mut i = 0; + for chr in line.chars() { + if i < start_char { + merged_content.push(chr); + } else { + break; + }; + i += chr.len_utf16(); + } + } + merged_content.push_str(content); + + if let Some(line) = self.lines.get(end_line) { + let mut i = 0; + for chr in line.chars() { + if i >= end_char { + merged_content.push(chr); + }; + i += chr.len_utf16(); + } + } + + let last_line_index = self.lines.len() - 1; + if (end.line as usize) < last_line_index + && merged_content.chars().last().unwrap_or('\0') != '\n' + { + merged_content.push('\n'); + } + + let end_line = std::cmp::min(self.lines.len().saturating_sub(1), end_line); + self.lines + .splice(start_line..=end_line, split_lines(&merged_content)) + .count(); + } +} + +/// Split code into several lines +fn split_lines(code: &str) -> Vec { + let mut lines = Vec::new(); + let bytes = code.as_bytes(); + + let mut i = 0; + let mut start = 0; + while i < bytes.len() { + let byte = bytes[i]; + + if byte == b'\n' { + i += 1; + let line = bytes[start..i].to_owned(); + let line = unsafe { String::from_utf8_unchecked(line) }; + lines.push(line); + start = i; + } else if byte == b'\r' { + i += 1; + let mut line = bytes[start..i].to_owned(); + let last = line.len().saturating_sub(1); + line[last] = b'\n'; + let line = unsafe { String::from_utf8_unchecked(line) }; + lines.push(line); + + if bytes.get(i) == Some(&b'\n') { + i += 1; + } + + start = i; + } else { + i += 1; + } + } + + if start < bytes.len() { + let bytes = bytes[start..].to_owned(); + let line = unsafe { String::from_utf8_unchecked(bytes) }; + lines.push(line); + } + lines +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ReaderState { + pos: Position, + idx: usize, // Byte offset in line +} + +impl ReaderState { + pub fn pos(&self) -> Position { + self.pos + } +} + +#[derive(Clone)] +pub struct ContentReader<'a> { + contents: &'a Contents, + state: ReaderState, +} + +impl<'a> ContentReader<'a> { + pub fn new(contents: &'a Contents) -> ContentReader<'a> { + ContentReader { + contents, + state: ReaderState { + pos: Position::default(), + idx: 0, + }, + } + } + + #[must_use] + pub fn get_char(&self) -> Option { + if let Some(line) = self.contents.get_line(self.state.pos.line as usize) { + let bytes = line.as_bytes(); + let idx = self.state.idx; + if idx < bytes.len() { + let slice = unsafe { std::str::from_utf8_unchecked(&bytes[idx..]) }; + slice.chars().next() + } else { + None + } + } else { + None + } + } + + pub fn pop(&mut self) -> Result, Utf8ToLatin1Error> { + if let Some(chr) = self.get_char() { + if let Some(latin1) = char_to_latin1(chr) { + self.skip_char(chr); + Ok(Some(latin1)) + } else { + let pos = self.pos(); + self.skip_char(chr); + Err(Utf8ToLatin1Error { pos, value: chr }) + } + } else { + Ok(None) + } + } + + pub fn get(&self) -> Result, Utf8ToLatin1Error> { + if let Some(chr) = self.get_char() { + if let Some(latin1) = char_to_latin1(chr) { + Ok(Some(latin1)) + } else { + Err(Utf8ToLatin1Error { + pos: self.pos(), + value: chr, + }) + } + } else { + Ok(None) + } + } + + #[must_use] + pub fn pop_char(&mut self) -> Option { + let chr = self.get_char()?; + self.skip_char(chr); + Some(chr) + } + + fn skip_char(&mut self, chr: char) { + self.state.pos.move_after_char(chr); + if self.state.pos.character == 0 { + self.state.idx = 0; + } else { + self.state.idx += chr.len_utf8(); + } + } + + pub fn skip(&mut self) { + let _ = self.pop_char(); + } + + pub fn pop_lowercase(&mut self) -> Result, Utf8ToLatin1Error> { + Ok(self.pop()?.map(Latin1String::lowercase)) + } + + pub fn peek_lowercase(&mut self) -> Result, Utf8ToLatin1Error> { + Ok(self.peek()?.map(Latin1String::lowercase)) + } + + /// Returns whether the chars that follow are the given substring. + /// + /// # Examples + /// ``` + /// use super::{ContentReader, Contents}; + /// let reader = ContentReader::new(Contents::from_str("foo bar")); + /// assert!(reader.matches("foo")); + /// assert!(!reader.matches("bar")); + /// for _ in 0..3 { + /// reader.skip(); + /// } + /// assert!(reader.matches("bar")); + /// assert!(!reader.matches("foo")); + /// ``` + #[cfg(test)] + pub fn matches(&mut self, substr: &str) -> bool { + let mut lookahead = self.clone(); + for exp in substr.chars() { + if let Some(chr) = lookahead.pop_char() { + if chr != exp { + return false; + } + } else { + return false; + } + } + true + } + + pub fn skip_if(&mut self, value: u8) -> Result { + if self.peek()? == Some(value) { + self.skip(); + Ok(true) + } else { + Ok(false) + } + } + + pub fn set_state(&mut self, state: ReaderState) { + self.state = state; + } + + pub fn set_to(&mut self, reader: &ContentReader<'_>) { + self.state = reader.state; + } + + pub fn pos(&self) -> Position { + self.state.pos() + } + + #[cfg(test)] + pub fn seek_pos(&mut self, pos: Position) { + self.state = ReaderState { + pos: Position { + line: pos.line, + character: 0, + }, + idx: 0, + }; + while self.pos() < pos { + self.skip(); + } + assert_eq!(self.pos(), pos); + } + + pub fn state(&self) -> ReaderState { + self.state + } + + pub fn peek(&self) -> Result, Utf8ToLatin1Error> { + self.get() + } + + pub fn peek_char(&self) -> Option { + self.get_char() + } + + pub fn value_at(&self, line: usize, start: usize, stop: usize) -> Option { + let line = self.contents.get_line(line)?; + if stop > line.len() { + return None; + } + Latin1String::from_utf8(&line[start..stop]).ok() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn new(code: &str) -> Contents { + Contents::from_str(code) + } + + fn reader(contents: &Contents) -> ContentReader<'_> { + ContentReader::new(contents) + } + + #[test] + fn pop_latin1_ok() { + let contents = new("hi"); + let mut reader = reader(&contents); + assert_eq!(reader.pop(), Ok(Some(b'h'))); + assert_eq!(reader.pop(), Ok(Some(b'i'))); + assert_eq!(reader.pop(), Ok(None)); + } + + #[test] + fn pop_latin1_err() { + let contents = new("h€i"); + let mut reader = reader(&contents); + assert_eq!(reader.pop(), Ok(Some(b'h'))); + assert_eq!( + reader.pop(), + Err(Utf8ToLatin1Error { + pos: Position::new(0, 1), + value: '€' + }) + ); + assert_eq!(reader.pop(), Ok(Some(b'i'))); + assert_eq!(reader.pop(), Ok(None)); + } + + #[test] + fn pop_single_line() { + let contents = new("hi"); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('h')); + assert_eq!(reader.pop_char(), Some('i')); + assert_eq!(reader.pop_char(), None); + } + + #[test] + fn pop_char() { + let contents = new("hå"); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('h')); + assert_eq!(reader.pop_char(), Some('å')); + assert_eq!(reader.pop_char(), None); + } + + #[test] + fn pop_multi_line_no_newline_at_end() { + let contents = new("h\ni"); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('h')); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), Some('i')); + assert_eq!(reader.pop_char(), None); + } + + #[test] + fn pop_multi_line() { + let contents = new("h\ni\n"); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('h')); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), Some('i')); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), None); + } + + #[test] + fn empty_lines() { + let contents = new("\n\n\n"); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), Some('\n')); + } + + #[test] + fn peek() { + let contents = new("hi"); + let mut reader = reader(&contents); + assert_eq!(reader.peek_char(), Some('h')); + assert_eq!(reader.pop_char(), Some('h')); + assert_eq!(reader.peek_char(), Some('i')); + assert_eq!(reader.pop_char(), Some('i')); + assert_eq!(reader.peek_char(), None); + assert_eq!(reader.pop_char(), None); + } + + #[test] + fn cr_is_removed() { + let contents = new("1\r2\r\n"); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('1')); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), Some('2')); + assert_eq!(reader.pop_char(), Some('\n')); + assert_eq!(reader.pop_char(), None); + } + + #[test] + fn matches() { + let contents = new("abc"); + let mut reader = reader(&contents); + assert!(reader.matches("abc")); + assert!(!reader.matches("bc")); + reader.skip(); + assert!(reader.matches("bc")); + } + + #[test] + fn character_is_utf16_len() { + // Bomb emojii requires 2 utf-16 codes + let bomb = '\u{1F4A3}'; + let contents = new(&format!("aä{bomb}")); + assert_eq!(contents.end(), Position::new(0, 4)); + let mut reader = reader(&contents); + assert_eq!(reader.pop_char(), Some('a')); + assert_eq!(reader.pos(), Position::new(0, 1)); + assert_eq!(reader.pop_char(), Some('ä')); + assert_eq!(reader.pos(), Position::new(0, 2)); + assert_eq!(reader.pop_char(), Some(bomb)); + assert_eq!(reader.pos(), Position::new(0, 4)); + } + + fn flatten(contents: &Contents) -> String { + let mut result = String::new(); + for line in contents.lines.iter() { + result.push_str(line); + } + result + } + + #[test] + fn change_first() { + let mut contents = new("hello"); + assert_eq!(flatten(&contents), "hello"); + contents.change(&Range::new(Position::new(0, 0), Position::new(0, 1)), "_"); + assert_eq!(flatten(&contents), "_ello"); + } + + #[test] + fn change_last() { + let mut contents = new("hello"); + assert_eq!(flatten(&contents), "hello"); + contents.change(&Range::new(Position::new(0, 4), Position::new(0, 5)), "_"); + assert_eq!(flatten(&contents), "hell_"); + } + + #[test] + fn change_middle() { + let mut contents = new("hello"); + assert_eq!(flatten(&contents), "hello"); + contents.change(&Range::new(Position::new(0, 2), Position::new(0, 4)), "__"); + assert_eq!(flatten(&contents), "he__o"); + } + + #[test] + fn change_shrink() { + let mut contents = new("hello"); + assert_eq!(flatten(&contents), "hello"); + contents.change(&Range::new(Position::new(0, 2), Position::new(0, 4)), "_"); + assert_eq!(flatten(&contents), "he_o"); + } + + #[test] + fn change_grow() { + let mut contents = new("hello"); + assert_eq!(flatten(&contents), "hello"); + contents.change(&Range::new(Position::new(0, 2), Position::new(0, 4)), "___"); + assert_eq!(flatten(&contents), "he___o"); + } + + #[test] + fn change_multi_line() { + let mut contents = new("hello\nworld"); + assert_eq!(flatten(&contents), "hello\nworld"); + contents.change( + &Range::new(Position::new(0, 3), Position::new(1, 2)), + "__\n__", + ); + assert_eq!(flatten(&contents), "hel__\n__rld"); + assert_eq!(contents.num_lines(), 2); + assert_eq!(contents.get_line(0).unwrap().to_string(), "hel__\n"); + assert_eq!(contents.get_line(1).unwrap().to_string(), "__rld"); + } + + #[test] + fn change_to_less_lines() { + let mut contents = new("hello\nworld"); + assert_eq!(flatten(&contents), "hello\nworld"); + contents.change(&Range::new(Position::new(0, 3), Position::new(1, 2)), ""); + assert_eq!(flatten(&contents), "helrld"); + assert_eq!(contents.num_lines(), 1); + assert_eq!(contents.get_line(0).unwrap().to_string(), "helrld"); + } + + #[test] + fn change_past_end_of_line() { + let mut contents = new("hello\nworld"); + assert_eq!(flatten(&contents), "hello\nworld"); + contents.change(&Range::new(Position::new(0, 3), Position::new(0, 7)), ""); + assert_eq!(flatten(&contents), "hel\nworld"); + assert_eq!(contents.num_lines(), 2); + assert_eq!(contents.get_line(0).unwrap().to_string(), "hel\n"); + assert_eq!(contents.get_line(1).unwrap().to_string(), "world"); + } + + #[test] + fn change_to_more_lines() { + let mut contents = new("hello\nworld"); + assert_eq!(flatten(&contents), "hello\nworld"); + contents.change( + &Range::new(Position::new(0, 3), Position::new(1, 2)), + "\nmiddle\n", + ); + assert_eq!(flatten(&contents), "hel\nmiddle\nrld"); + assert_eq!(contents.num_lines(), 3); + assert_eq!(contents.get_line(0).unwrap().to_string(), "hel\n"); + assert_eq!(contents.get_line(1).unwrap().to_string(), "middle\n"); + assert_eq!(contents.get_line(2).unwrap().to_string(), "rld"); + } + + #[test] + fn change_keeps_surrounding_lines() { + let mut contents = new("___\nhello\nworld\n..."); + assert_eq!(flatten(&contents), "___\nhello\nworld\n..."); + contents.change(&Range::new(Position::new(1, 3), Position::new(2, 2)), ""); + assert_eq!(flatten(&contents), "___\nhelrld\n..."); + assert_eq!(contents.num_lines(), 3); + assert_eq!(contents.get_line(0).unwrap().to_string(), "___\n"); + assert_eq!(contents.get_line(1).unwrap().to_string(), "helrld\n"); + assert_eq!(contents.get_line(2).unwrap().to_string(), "..."); + } + + #[test] + fn change_empty() { + let mut contents = new(""); + assert_eq!(flatten(&contents), ""); + contents.change(&Range::new(Position::new(0, 0), Position::new(0, 0)), "H"); + assert_eq!(flatten(&contents), "H"); + } + + #[test] + fn change_to_empty() { + let mut contents = new("H"); + assert_eq!(flatten(&contents), "H"); + contents.change(&Range::new(Position::new(0, 0), Position::new(0, 1)), ""); + assert_eq!(flatten(&contents), ""); + } + + #[test] + fn change_add_missing_newline() { + // LSP client will use end line outside of text + let mut contents = new("a"); + assert_eq!(flatten(&contents), "a"); + contents.change(&Range::new(Position::new(0, 1), Position::new(1, 0)), "\n"); + assert_eq!(flatten(&contents), "a\n"); + assert_eq!(contents.num_lines(), 1); + assert_eq!(contents.get_line(0).unwrap().to_string(), "a\n"); + } +} diff --git a/vhdl_lang/src/data/diagnostic.rs b/vhdl_lang/src/data/diagnostic.rs new file mode 100644 index 0000000..99c119e --- /dev/null +++ b/vhdl_lang/src/data/diagnostic.rs @@ -0,0 +1,238 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use super::SrcPos; +use crate::data::error_codes::{ErrorCode, SeverityMap}; +use std::convert::{AsRef, Into}; +use strum::{EnumString, IntoStaticStr}; + +#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, EnumString, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum Severity { + Hint, + Info, + Warning, + Error, +} + +#[must_use] +#[derive(PartialEq, Debug, Clone, Eq, Hash)] +pub struct Diagnostic { + pub pos: SrcPos, + pub message: String, + pub related: Vec<(SrcPos, String)>, + pub code: ErrorCode, +} + +impl Diagnostic { + pub fn new(item: impl AsRef, msg: impl Into, code: ErrorCode) -> Diagnostic { + Diagnostic { + pos: item.as_ref().clone(), + message: msg.into(), + related: vec![], + code, + } + } + + pub fn when(self, message: impl AsRef) -> Diagnostic { + Diagnostic { + message: format!("{}, when {}", &self.message, message.as_ref()), + pos: self.pos, + related: vec![], + code: self.code, + } + } + + pub fn related(self, item: impl AsRef, message: impl Into) -> Diagnostic { + let mut diagnostic = self; + diagnostic.add_related(item, message); + diagnostic + } + + pub fn opt_related( + self, + item: Option>, + message: impl Into, + ) -> Diagnostic { + let mut diagnostic = self; + if let Some(item) = item { + diagnostic.add_related(item, message); + } + diagnostic + } + + pub fn add_related(&mut self, item: impl AsRef, message: impl Into) { + self.related + .push((item.as_ref().to_owned(), message.into())); + } + + pub fn drain_related(&mut self) -> Vec { + let mut diagnostics = Vec::with_capacity(self.related.len()); + let related = std::mem::take(&mut self.related); + for (pos, msg) in related { + diagnostics.push(Diagnostic::new( + pos, + format!("related: {msg}"), + ErrorCode::Related, + )); + } + diagnostics + } + + pub fn show(&self, severities: &SeverityMap) -> Option { + let severity = severities[self.code]?; + let mut result = String::new(); + for (pos, message) in self.related.iter() { + result.push_str(&pos.show(&format!("related: {message}"))); + result.push('\n'); + } + let severity: &str = severity.into(); + result.push_str(&self.pos.show(&format!("{}: {}", severity, self.message))); + Some(result) + } + + #[cfg(test)] + pub fn show_default(&self) -> String { + self.show(&SeverityMap::default()) + .expect("All severities should be defined in the default severity map") + } +} + +pub type DiagnosticResult = Result; + +pub trait DiagnosticHandler { + fn push(&mut self, diagnostic: Diagnostic); +} + +impl<'a> dyn DiagnosticHandler + 'a { + pub fn add(&mut self, item: impl AsRef, msg: impl Into, code: ErrorCode) { + self.push(Diagnostic::new(item, msg, code)) + } + + pub fn push_result(&mut self, diagnostic: Result) { + if let Err(diagnostic) = diagnostic { + self.push(diagnostic); + } + } + + pub fn push_some(&mut self, diagnostic: Option) { + if let Some(diagnostic) = diagnostic { + self.push(diagnostic); + } + } + + pub fn append(&mut self, diagnostics: impl IntoIterator) { + for diagnostic in diagnostics.into_iter() { + self.push(diagnostic); + } + } +} + +impl DiagnosticHandler for Vec { + fn push(&mut self, diagnostic: Diagnostic) { + self.push(diagnostic) + } +} + +pub struct NullDiagnostics; + +impl DiagnosticHandler for NullDiagnostics { + fn push(&mut self, _diagnostic: Diagnostic) { + // Ignore + } +} + +#[cfg(test)] +pub struct NoDiagnostics; + +#[cfg(test)] +impl DiagnosticHandler for NoDiagnostics { + fn push(&mut self, diagnostic: Diagnostic) { + panic!("{}", diagnostic.show_default()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::test::Code; + use std::path::Path; + + #[test] + fn show_warning() { + let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n"); + assert_eq!( + Diagnostic::new(code.s1("world"), "Greetings", ErrorCode::Unused).show_default(), + "\ +warning: Greetings + --> {unknown file}:2 + | +1 | hello +2 --> world + | ~~~~~ +3 | line +" + ); + } + + #[test] + fn show_error() { + let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n"); + assert_eq!( + Diagnostic::new(code.s1("world"), "Greetings", ErrorCode::SyntaxError).show_default(), + "\ +error: Greetings + --> {unknown file}:2 + | +1 | hello +2 --> world + | ~~~~~ +3 | line +" + ); + } + + #[test] + fn show_related() { + let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n"); + + let err = Diagnostic::new(code.s1("line"), "Greetings", ErrorCode::SyntaxError) + .related(code.s1("hello"), "From here"); + + assert_eq!( + err.show_default(), + "\ +related: From here + --> {unknown file}:1 + | +1 --> hello + | ~~~~~ +2 | world +3 | line + +error: Greetings + --> {unknown file}:3 + | +1 | hello +2 | world +3 --> line + | ~~~~ +" + ); + } + + #[test] + fn do_not_show_ignored_errors() { + let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n"); + let mut severity_map = SeverityMap::default(); + severity_map[ErrorCode::Unused] = None; + + assert_eq!( + Diagnostic::new(code.s1("world"), "Greetings", ErrorCode::Unused).show(&severity_map), + None + ); + } +} diff --git a/vhdl_lang/src/data/error_codes.rs b/vhdl_lang/src/data/error_codes.rs new file mode 100644 index 0000000..3eb776a --- /dev/null +++ b/vhdl_lang/src/data/error_codes.rs @@ -0,0 +1,586 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com + +use crate::{Diagnostic, Severity, SrcPos}; +use enum_map::{enum_map, Enum, EnumMap}; +use std::fmt::{Display, Formatter}; +use std::ops::{Index, IndexMut}; +use strum::{EnumString, IntoStaticStr}; + +#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, EnumString, IntoStaticStr, Enum)] +#[strum(serialize_all = "snake_case")] +pub enum ErrorCode { + /// A syntax error happens during tokenization or parsing. + /// + /// # Example + /// ```vhdl + /// entity foo is + /// port ( + /// clk: in bit; + /// ^ Last interface element may not end with ';' + /// ); + /// end entity; + /// ``` + SyntaxError, + + // Analysis + /// A circular dependency was found where one module depends on another module which + /// (directly or indirectly) again depends on the first module. + /// + /// # Example + /// ```vhdl + /// use work.bar; + /// + /// package foo is + /// end package; + /// + /// use work.foo; + /// + /// package bar is + /// end package; + /// ``` + CircularDependency, + + /// A formal parameter is invalid / malformed in a certain context + /// + /// # Example + /// ```vhdl + /// constant x : bit := foo(b.all => '0'); + /// ``` + InvalidFormal, + + /// Invalid conversion from a formal parameter + /// + /// # Example + /// ```vhdl + /// entity foo is + /// port ( + /// bar: out natural + /// ); + /// end entity; + /// + /// -- When instantiating foo + /// function fun1(arg : natural) return natural; + /// + /// foo_inst: entity work.foo + /// port map ( + /// fun1(arg => bar) => sig + /// ); + /// ``` + InvalidFormalConversion, + + /// Issued when two types don't match in a context where they should. + /// + /// # Example + /// ```vhdl + /// constant x : integer := 'a'; + /// ``` + TypeMismatch, + + /// There are multiple functions that a call could address. + /// All methods to disambiguate are exhausted. + /// + /// # Example + /// ```vhdl + /// function foo return integer; + /// function foo return character; + /// function bar(arg: integer) return integer; + /// function bar(arg: character) return integer; + /// constant baz: integer := bar(foo); + /// ``` + AmbiguousCall, + + /// Named arguments appear before positional arguments when calling a function + /// + /// # Example + /// ```vhdl + /// function foo(a, b: integer) return bit; + /// constant bar: bit := foo(a => 1, 2); + /// ``` + NamedBeforePositional, + + /// When calling a function, more arguments are passed to that function than it can accept. + /// + /// # Example + /// ```vhdl + /// function foo(a, b: integer) return bit; + /// constant bar: bit := foo(1, 2, 3); + /// ``` + TooManyArguments, + + /// A formal parameter wasn't associated in a function call. + /// Also emitted when a record element was not associated in a view mode declaration. + /// + /// # Example + /// ```vhdl + /// function foo(a, b: integer) return bit; + /// constant bar: bit := foo(a => 1); + /// ``` + Unassociated, + + /// A formal element has already been associated + /// + /// # Example + /// ```vhdl + /// function foo(a: integer) return bit; + /// constant bar: bit := foo(a => 1, a => 2); + /// ``` + AlreadyAssociated, + + /// The interface mode of a formal parameter (i.e., `signal`, `variable`, ...) + /// of a function does not match the declared more + /// + /// # Example + /// ```vhdl + /// function foo(signal a: integer) return bit; + /// constant bar: bit := foo(a => 1); -- a must be associated to a signal, not a constant + /// ``` + InterfaceModeMismatch, + + /// An element is not allowed inside a sensitivity list + /// + /// # Example + /// ```vhdl + /// architecture foo of bar is + /// shared variable x : bit; + /// begin + /// + /// process (x) + /// begin + /// end process; + /// + /// end architecture; + /// ``` + DisallowedInSensitivityList, + + /// A declaration is not allowed in a certain context. + /// For example, in an architecture declarative part, + /// `signal`s, `constant`s or `shared variable`s can be declared. + /// However, variables may not be declared in that context. + /// + /// # Example + /// ```vhdl + /// architecture foo of bar is + /// variable baz : bit; + /// begin + /// end architecture; + /// ``` + DeclarationNotAllowed, + + /// The entity class of an attribute does not match the declared + /// + /// # Example + /// ```vhdl + /// signal bad : boolean; + /// attribute foo : boolean; + /// attribute foo of bad : variable is true; -- should be signal, not variable + /// ``` + MismatchedEntityClass, + + /// The attribute specification is not in the immediate declarative part + /// + /// # Example + /// ```vhdl + /// entity bar is + /// port ( + /// a : in bit + /// ); + /// end entity; + /// + /// architecture foo of bar is + /// attribute baz : string; + /// attribute baz of a : signal is "foobar"; + /// begin + /// end architecture; + /// ``` + MisplacedAttributeSpec, + + /// There is no overloaded function with an explicitly provided signature available + /// + /// # Example + /// + /// ```vhdl + /// function foo return natural; + /// attribute bar : natural; + /// + /// attribute bar of foo[return boolean] : function is 0; + /// ``` + NoOverloadedWithSignature, + + /// An explicit signature is used in a context where no signature is expected. + /// + /// # Example + /// ```vhdl + /// type enum_t is (alpha, beta); + /// alias alias_t is enum_t[return integer]; + /// ``` + IllegalSignature, + + /// A signature is required to disambiguate + /// + /// # Example + /// ```vhdl + /// procedure foo(arg: natural); + /// alias bar is subpgm; + /// ``` + SignatureRequired, + + /// The value of an expression is ambiguous + AmbiguousExpression, + + /// A declaration was already declared previously + /// + /// # Example + /// ```vhdl + /// constant foo: bit := '0'; + /// constant foo: bit := '1'; + /// ``` + Duplicate, + + /// A designator is hidden by a conflicting use clause + ConflictingUseClause, + + /// A protected type that does not have a body + /// + /// # Example + /// + /// ```vhdl + /// type a1 is protected + /// end protected; + /// + /// -- No `type a1 is protected body ... follows` + /// ``` + MissingProtectedBodyType, + + /// A deferred constant is not allowed in the given context + IllegalDeferredConstant, + + /// The signature between an uninstantiated subprogram and it's instantiated + /// counterpart does not match + /// + /// # Example + /// ```vhdl + /// procedure foo + /// generic (type T) + /// parameter (x : bit) + /// is begin + /// end foo; + /// + /// procedure proc is new foo [bit, bit]; + /// ``` + SignatureMismatch, + + /// When instantiating an uninstantiated subprogram, no distinct subprogram is available + AmbiguousInstantiation, + + /// Instantiating a function as procedure or vice-versa + MismatchedSubprogramInstantiation, + + /// Function returns without a value + VoidReturn, + + /// Procedure returns with value + NonVoidReturn, + + /// Illegal return statement, for example in a process + /// + /// # Example + /// + /// ```vhdl + /// process (clk) + /// begin + /// if rising_edge(clk) then + /// return; + /// end if; + /// end process; + /// ``` + IllegalReturn, + + /// Exit statement called outside a loop + ExitOutsideLoop, + + /// Next statement called outside a loop + NextOutsideLoop, + + /// A loop label was found at a position where it shouldn't be + /// + /// # Example + /// ```vhdl + /// bad0: loop + /// end loop; + /// + /// loop + /// exit bad0; + /// end loop; + /// ``` + InvalidLoopLabel, + + /// Got something (a named entity such as a type, procedure, e.t.c.) + /// while expecting another thing. For example, got something that names a procedure while + /// expecting a type name. + /// + /// This is different from a type error. When a type error occurs, + /// the kinds already match. + MismatchedKinds, + + /// An extra index constraint is present + TooManyConstraints, + + /// There are not enough constraints + TooFewConstraints, + + /// A constraint cannot be used for a given type + IllegalConstraint, + + /// A string or symbol was used in a context where an operator was expected but there + /// is no operator for that string. + InvalidOperatorSymbol, + + /// An unresolved name was used + /// + /// # Example + /// ```vhdl + /// -- There is nothing named 'bar' in this scope + /// variable foo: integer = bar; + /// ``` + Unresolved, + + /// An index that is out of range for an N-Dimensional array + DimensionMismatch, + + /// A literal that cannot be assigned to its target type + InvalidLiteral, + + /// A Design Unit (such as an architecture) was declared before another + ///Design Unit (such as an entity) which is illegal. + DeclaredBefore, + + /// A configuration was found that is not in the same library as the entity + ConfigNotInSameLibrary, + + /// No implicit conversion using the `??` operator is possible + NoImplicitConversion, + + /// Expected sub-aggregate + ExpectedSubAggregate, + + /// An attribute was used on an element that it cannot be used on + IllegalAttribute, + + /// Something cannot be prefixed + CannotBePrefixed, + + /// A non-scalar is used in a range + NonScalarInRange, + + /// A signature appeared that was not expected + UnexpectedSignature, + + /// A deferred constant is missing its full constant declaration in the package body + MissingDeferredDeclaration, + + /// A deferred type declaration is missing its full declaration + MissingFullTypeDeclaration, + + /// Calling a name like a function or procedure where that is not applicable + InvalidCall, + + // Linting + /// A declaration that is unused + Unused, + + /// The declaration + /// ```vhdl + /// library work; + /// ``` + /// was made. + UnnecessaryWorkLibrary, + + /// A context clause that is not associated to a design unit + /// + /// # Example + /// ```vhdl + /// library ieee; + /// use ieee.std_logic_1164.all; + /// + /// -- End of file + /// ``` + UnassociatedContext, + + // Misc + /// An internal error that signifies that some precondition within vhdl_lang wasn't met. + /// If an error with this error code occurs, + /// please file an issue at https://github.com/VHDL-LS/rust_hdl/issues + Internal, + + /// A related error message. This error code is never generated directly and only used + /// as 'drop-in' when related messages are drained from a bigger error message + Related, +} + +/// The `SeverityMap` maps error codes to severities. +/// +/// Implementations for `Index` and `IndexMut` are provided, so elements within the map can +/// be accessed using the `[]` operator. +/// The value returned by indexing into the severity map has the following meaning: +/// * If the value is `Some(Severity)`, +/// a diagnostic with the given error code should be displayed with that severity +/// * If the value is `None`, a diagnostic with that severity should not be displayed +#[derive(Clone, PartialEq, Eq, Debug, Copy)] +pub struct SeverityMap { + // Using an `EnumMap` ensures that each error code is mapped to exactly one severity. + // Additionally, this allows efficient implementation using an array internally. + inner: EnumMap>, +} + +impl Default for SeverityMap { + fn default() -> Self { + use ErrorCode::*; + use Severity::*; + let map = enum_map! { + SyntaxError + | CircularDependency + | InvalidFormal + | InvalidFormalConversion + | TypeMismatch + | AmbiguousCall + | NamedBeforePositional + | TooManyArguments + | Unassociated + | AlreadyAssociated + | InterfaceModeMismatch + | DisallowedInSensitivityList + | DeclarationNotAllowed + | MismatchedEntityClass + | MisplacedAttributeSpec + | NoOverloadedWithSignature + | IllegalSignature + | SignatureRequired + | AmbiguousExpression + | Duplicate + | ConflictingUseClause + | MissingProtectedBodyType + | IllegalDeferredConstant + | SignatureMismatch + | AmbiguousInstantiation + | MismatchedSubprogramInstantiation + | VoidReturn + | NonVoidReturn + | IllegalReturn + | ExitOutsideLoop + | NextOutsideLoop + | InvalidLoopLabel + | MismatchedKinds + | TooManyConstraints + | TooFewConstraints + | IllegalConstraint + | InvalidOperatorSymbol + | Unresolved + | DimensionMismatch + | InvalidLiteral + | DeclaredBefore + | ConfigNotInSameLibrary + | NoImplicitConversion + | ExpectedSubAggregate + | IllegalAttribute + | CannotBePrefixed + | NonScalarInRange + | UnexpectedSignature + | MissingDeferredDeclaration + | MissingFullTypeDeclaration + | InvalidCall => Some(Error), + Unused + | UnnecessaryWorkLibrary + | UnassociatedContext => Some(Warning), + Internal => Some(Error), + Related => Some(Hint) + }; + SeverityMap { inner: map } + } +} + +impl Index for SeverityMap { + type Output = Option; + + fn index(&self, key: ErrorCode) -> &Self::Output { + self.inner.index(key) + } +} + +impl IndexMut for SeverityMap { + fn index_mut(&mut self, key: ErrorCode) -> &mut Self::Output { + self.inner.index_mut(key) + } +} + +impl ErrorCode { + pub fn as_str(&self) -> &str { + self.into() + } +} + +#[test] +fn serialize_from_string() { + assert_eq!( + ErrorCode::try_from("void_return"), + Ok(ErrorCode::VoidReturn) + ); + assert_eq!( + ErrorCode::try_from("misplaced_attribute_spec"), + Ok(ErrorCode::MisplacedAttributeSpec) + ); + assert_eq!( + ErrorCode::try_from("syntax_error"), + Ok(ErrorCode::SyntaxError) + ); + assert_eq!( + ErrorCode::try_from("not_an_error_code"), + Err(strum::ParseError::VariantNotFound) + ); +} + +#[test] +fn serialize_to_string() { + assert_eq!(ErrorCode::VoidReturn.as_str(), "void_return"); + assert_eq!( + ErrorCode::MisplacedAttributeSpec.as_str(), + "misplaced_attribute_spec" + ); + assert_eq!(ErrorCode::SyntaxError.as_str(), "syntax_error"); +} + +/// Specialized diagnostics with pre-defined messages and error codes +impl Diagnostic { + pub fn syntax_error(item: impl AsRef, msg: impl Into) -> Diagnostic { + Self::new(item, msg, ErrorCode::SyntaxError) + } + + pub fn circular_dependency(item: impl AsRef) -> Diagnostic { + Self::new( + item, + "Found circular dependency", + ErrorCode::CircularDependency, + ) + } + + pub fn internal(item: impl AsRef, msg: impl Into) -> Diagnostic { + Self::new(item, msg, ErrorCode::Internal) + } + + pub fn illegal_attribute(item: impl AsRef, msg: impl Into) -> Diagnostic { + Self::new(item, msg, ErrorCode::IllegalAttribute) + } + + pub fn mismatched_kinds(item: impl AsRef, msg: impl Into) -> Diagnostic { + Self::new(item, msg, ErrorCode::MismatchedKinds) + } +} + +impl Display for ErrorCode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} diff --git a/vhdl_lang/src/data/latin_1.rs b/vhdl_lang/src/data/latin_1.rs new file mode 100644 index 0000000..0548c46 --- /dev/null +++ b/vhdl_lang/src/data/latin_1.rs @@ -0,0 +1,267 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use super::Position; +use std::fmt; +use std::str; + +pub fn iso_8859_1_to_utf8(bytes: &[u8]) -> String { + let mut utf8_bytes = Vec::new(); + for byte in bytes.iter() { + let byte = *byte; + if byte < 128 { + utf8_bytes.push(byte); + } else if byte < 192 { + utf8_bytes.push(0xc2); + utf8_bytes.push(byte); + } else { + utf8_bytes.push(0xc3); + utf8_bytes.push(byte - 64); + } + } + unsafe { str::from_utf8_unchecked(utf8_bytes.as_slice()).to_string() } +} + +pub fn char_to_latin1(chr: char) -> Option { + let mut bytes = [0; 4]; + chr.encode_utf8(&mut bytes); + let byte = bytes[0]; + if byte < 128 { + Some(byte) + } else if byte == 0xc2 { + let next_byte = bytes[1]; + if (128..192).contains(&next_byte) { + Some(next_byte) + } else { + None + } + } else if byte == 0xc3 { + let next_byte = bytes[1]; + if (128..192).contains(&next_byte) { + Some(next_byte + 64) + } else { + None + } + } else { + None + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct Latin1String { + pub bytes: Vec, +} + +impl Latin1String { + pub fn empty() -> Latin1String { + Latin1String { bytes: Vec::new() } + } + + pub fn new(bytes: &[u8]) -> Latin1String { + Latin1String { + bytes: Vec::from(bytes), + } + } + + pub fn chars(&self) -> impl Iterator { + self.bytes.iter() + } + + pub fn from_vec(bytes: Vec) -> Latin1String { + Latin1String { bytes } + } + + pub fn len(&self) -> usize { + self.bytes.len() + } + + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + pub fn lowercase(chr: u8) -> u8 { + match chr { + 215 => chr, + b'A'..=b'Z' | 192..=214 | 216..=222 => chr + 32, + _ => chr, + } + } + + pub fn make_lowercase(&mut self) { + for i in 0..self.bytes.len() { + self.bytes[i] = Self::lowercase(self.bytes[i]); + } + } + + pub fn to_lowercase(&self) -> Latin1String { + let mut latin1 = Latin1String { + bytes: self.bytes.clone(), + }; + latin1.make_lowercase(); + latin1 + } + + pub fn starts_with(&self, other: &Latin1String) -> bool { + if other.len() <= self.len() { + self.bytes[0..other.len()] == other.bytes + } else { + false + } + } + + #[cfg(test)] + pub fn from_utf8_unchecked(string: &str) -> Latin1String { + Self::from_utf8(string).unwrap() + } + + pub fn from_utf8(string: &str) -> Result { + let bytes = string.as_bytes(); + let mut latin1_bytes = Vec::with_capacity(string.len()); + let mut i = 0; + + let mut line = 0; + let mut column = 0; + + while i < bytes.len() { + let byte = bytes[i]; + + let mut error = false; + if byte < 128 { + latin1_bytes.push(byte); + i += 1; + } else if byte == 0xc2 { + let next_byte = bytes[i + 1]; + if (128..192).contains(&next_byte) { + latin1_bytes.push(next_byte); + i += 2; + } else { + error = true; + } + } else if byte == 0xc3 { + let next_byte = bytes[i + 1]; + if (128..192).contains(&next_byte) { + latin1_bytes.push(next_byte + 64); + i += 2; + } else { + error = true; + } + } else { + error = true; + } + + if error { + let value = string[i..].chars().next().unwrap(); + return Err(Utf8ToLatin1Error { + pos: Position::new(line, column), + value, + }); + } + + if byte == b'\n' { + line += 1; + column = 0; + } else { + column += 1; + } + } + Ok(Latin1String::from_vec(latin1_bytes)) + } + + pub fn push(&mut self, byte: u8) { + self.bytes.push(byte) + } + + pub fn clear(&mut self) { + self.bytes.clear() + } + + pub fn append(&mut self, other: &mut Latin1String) { + self.bytes.append(&mut other.bytes); + } +} + +impl Default for Latin1String { + fn default() -> Self { + Self::empty() + } +} + +impl fmt::Debug for Latin1String { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", iso_8859_1_to_utf8(&self.bytes)) + } +} + +impl fmt::Display for Latin1String { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", iso_8859_1_to_utf8(&self.bytes)) + } +} + +#[derive(PartialEq, Eq, Debug)] +pub struct Utf8ToLatin1Error { + pub pos: Position, + pub value: char, +} + +impl Utf8ToLatin1Error { + pub fn message(&self) -> String { + format!("Found invalid latin-1 character '{}'", self.value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + + #[test] + fn latin1_to_utf8() { + for byte in 0..=255 { + let latin1 = Latin1String::new(&[byte]); + assert_eq!( + Latin1String::from_utf8(&latin1.to_string()).unwrap(), + latin1 + ) + } + } + + #[test] + fn latin1_lowercase() { + for byte in 0..=255 { + let latin1 = Latin1String::new(&[byte]); + let utf8 = latin1.to_string(); + assert_eq!(latin1.to_lowercase().to_string(), utf8.to_lowercase()); + } + } + + #[test] + fn utf8_to_latin1() { + let utf8 = "åäö"; + assert_matches!(Latin1String::from_utf8(utf8), Ok(latin1) => { + assert_eq!(latin1.bytes, [229, 228, 246]); + assert_eq!(latin1.to_string(), utf8); + }) + } + + #[test] + fn utf8_to_latin1_error() { + let utf8 = "abö€"; + assert_matches!(Latin1String::from_utf8(utf8), Err(err) => { + assert_eq!(err.pos.line, 0); + assert_eq!(err.pos.character, 3); + assert_eq!(err.value, '€'); + }); + + let utf8 = "a\nbö\n€"; + assert_matches!(Latin1String::from_utf8(utf8), Err(err) => { + assert_eq!(err.pos.line, 2); + assert_eq!(err.pos.character, 0); + assert_eq!(err.value, '€'); + assert_eq!(err.message(), "Found invalid latin-1 character '€'"); + }); + } +} diff --git a/vhdl_lang/src/data/message.rs b/vhdl_lang/src/data/message.rs new file mode 100644 index 0000000..e640194 --- /dev/null +++ b/vhdl_lang/src/data/message.rs @@ -0,0 +1,99 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com + +use std::path::Path; +use strum::AsRefStr; + +#[derive(Debug, PartialEq, Eq, AsRefStr)] +#[strum(serialize_all = "snake_case")] +pub enum MessageType { + Error, + Warning, + Info, + Log, +} + +#[must_use] +#[derive(Debug, PartialEq, Eq)] +pub struct Message { + pub message_type: MessageType, + pub message: String, +} + +impl Message { + pub fn log(message: impl Into) -> Message { + Message { + message_type: MessageType::Log, + message: message.into(), + } + } + + pub fn info(message: impl Into) -> Message { + Message { + message_type: MessageType::Info, + message: message.into(), + } + } + + pub fn warning(message: impl Into) -> Message { + Message { + message_type: MessageType::Warning, + message: message.into(), + } + } + + pub fn error(message: impl Into) -> Message { + Message { + message_type: MessageType::Error, + message: message.into(), + } + } + + pub fn file_error(message: impl Into, file_name: &Path) -> Message { + Message { + message_type: MessageType::Error, + message: format!( + "{} (In file {})", + message.into(), + file_name.to_string_lossy() + ), + } + } +} + +impl std::fmt::Display for Message { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.message_type.as_ref(), self.message) + } +} + +pub trait MessageHandler { + fn push(&mut self, message: Message); +} + +impl MessageHandler for Vec { + fn push(&mut self, message: Message) { + self.push(message) + } +} + +#[derive(Default)] +pub struct MessagePrinter {} + +impl MessageHandler for MessagePrinter { + fn push(&mut self, message: Message) { + // println!("{message}"); + } +} + +#[derive(Default)] +pub struct NullMessages; + +impl MessageHandler for NullMessages { + fn push(&mut self, _message: Message) { + // Ignore + } +} diff --git a/vhdl_lang/src/data/source.rs b/vhdl_lang/src/data/source.rs new file mode 100644 index 0000000..126c2bc --- /dev/null +++ b/vhdl_lang/src/data/source.rs @@ -0,0 +1,803 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use super::contents::Contents; +use parking_lot::{RwLock, RwLockReadGuard}; +use std::cmp::{max, min}; +use std::collections::hash_map::DefaultHasher; +use std::convert::AsRef; +use std::fmt; +use std::fmt::Write; +use std::hash::{Hash, Hasher}; +use std::io; +pub use std::path::{Path, PathBuf}; +use std::sync::Arc; + +#[derive(Debug)] +struct FileId { + name: FilePath, + /// Hash value of `self.name`. + hash: u64, +} + +impl FileId { + fn new(name: &Path) -> FileId { + let name = FilePath::new(name); + let hash = hash(&name); + Self { name, hash } + } +} + +impl PartialEq for FileId { + fn eq(&self, other: &Self) -> bool { + // Use file name hash to speedup comparison + if self.hash == other.hash { + self.name == other.name + } else { + false + } + } +} + +fn hash(value: &Path) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +/// Represents a single source file and its contents. +struct UniqueSource { + file_id: FileId, + contents: RwLock, +} + +impl fmt::Debug for UniqueSource { + /// Custom implementation to avoid large contents strings. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(UniqueSource)) + .field(stringify!(file_id), &self.file_id) + .field(stringify!(contents), &"...") + .finish() + } +} + +impl UniqueSource { + fn inline(file_name: &Path, contents: &str) -> Self { + Self { + file_id: FileId::new(file_name), + contents: RwLock::new(Contents::from_str(contents)), + } + } + + fn from_latin1_file(file_name: &Path) -> io::Result { + let contents = Contents::from_latin1_file(file_name)?; + Ok(Self { + file_id: FileId::new(file_name), + contents: RwLock::new(contents), + }) + } + + #[cfg(test)] + pub fn from_contents(file_name: &Path, contents: Contents) -> UniqueSource { + Self { + file_id: FileId::new(file_name), + contents: RwLock::new(contents), + } + } + + fn contents(&self) -> RwLockReadGuard<'_, Contents> { + self.contents.read() + } + + fn file_name(&self) -> &Path { + self.file_id.name.as_ref() + } + + fn file_path(&self) -> &FilePath { + &self.file_id.name + } +} + +/// A thread-safe reference to a source file. +/// Multiple objects of this type can refer to the same source. +#[derive(Debug, Clone)] +pub struct Source(Arc); + +impl PartialEq for Source { + fn eq(&self, other: &Self) -> bool { + self.0.file_id == other.0.file_id + } +} + +impl PartialOrd for Source { + fn partial_cmp(&self, other: &Source) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Source { + fn cmp(&self, other: &Source) -> std::cmp::Ordering { + self.file_name().cmp(other.file_name()) + } +} + +impl Eq for Source {} + +impl Hash for Source { + fn hash(&self, hasher: &mut H) { + hasher.write_u64(self.0.file_id.hash) + } +} + +impl Source { + /// Creates a source from a (virtual) name and in-memory contents. + /// + /// Note: For differing values of `contents`, the value of `file_name` + /// *must* differ as well. + pub fn inline(file_name: &Path, contents: &str) -> Source { + Source(Arc::new(UniqueSource::inline(file_name, contents))) + } + + pub fn from_latin1_file(file_name: &Path) -> io::Result { + Ok(Source(Arc::new(UniqueSource::from_latin1_file(file_name)?))) + } + + #[cfg(test)] + pub fn from_contents(file_name: &Path, contents: Contents) -> Source { + Source(Arc::new(UniqueSource::from_contents(file_name, contents))) + } + + pub fn contents(&self) -> RwLockReadGuard<'_, Contents> { + self.0.contents() + } + + pub fn file_name(&self) -> &Path { + self.0.file_name() + } + + pub(crate) fn file_path(&self) -> &FilePath { + self.0.file_path() + } + + pub fn pos(&self, start: Position, end: Position) -> SrcPos { + SrcPos { + source: self.clone(), + range: Range { start, end }, + } + } + + pub fn change(&self, range: Option<&Range>, content: &str) { + let mut contents = self.0.contents.write(); + if let Some(range) = range { + contents.change(range, content); + } else { + *contents = Contents::from_str(content); + } + } +} + +/// A lexical position (line, column) in a source. +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug, Default)] +pub struct Position { + /// Line (zero-based). + pub line: u32, + /// Column (zero-based). + pub character: u32, +} + +impl Position { + pub fn new(line: u32, character: u32) -> Position { + Position { line, character } + } + + pub fn next_char(self) -> Position { + Position { + line: self.line, + character: self.character + 1, + } + } + + pub fn move_after_char(&mut self, chr: char) { + if chr == '\n' { + self.line += 1; + self.character = 0; + } else { + self.character += chr.len_utf16() as u32; + } + } + + pub fn after_char(mut self, chr: char) -> Position { + self.move_after_char(chr); + self + } + + pub fn prev_char(self) -> Position { + Position { + line: self.line, + character: self.character.saturating_sub(1), + } + } + + pub fn range_to(self, end: Position) -> Range { + Range { start: self, end } + } +} + +/// A lexical range in a source. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +pub struct Range { + /// Start of the range (inclusive). + pub start: Position, + /// End of the range (exclusive). + pub end: Position, +} + +impl Range { + pub fn new(start: Position, end: Position) -> Range { + Range { start, end } + } + + pub fn contains(&self, position: Position) -> bool { + self.start <= position && self.end >= position + } +} + +/// A lexical range within a specific source file. +#[derive(PartialEq, Clone, Debug, Eq, Hash)] +pub struct SrcPos { + /// The referenced source file. + pub source: Source, + pub range: Range, +} + +impl Ord for SrcPos { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let ordering = self.source.cmp(&other.source); + if std::cmp::Ordering::Equal == ordering { + self.range.start.cmp(&other.range.start) + } else { + ordering + } + } +} + +impl PartialOrd for SrcPos { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl AsRef for SrcPos { + fn as_ref(&self) -> &SrcPos { + self + } +} + +impl SrcPos { + const LINE_CONTEXT: u32 = 2; + + pub fn new(source: Source, range: Range) -> SrcPos { + SrcPos { source, range } + } + + fn get_line_context(&self, context_lines: u32, contents: &Contents) -> Vec<(u32, String)> { + let mut lines = Vec::new(); + + let start = self.range.start.line.saturating_sub(context_lines); + let end = self.range.end.line + context_lines; + + for lineno in start..=end { + if let Some(line) = contents.get_line(lineno as usize) { + lines.push((lineno, line.to_owned())); + } + } + + if lines.is_empty() { + lines.push((self.range.start.line, String::new())); + } + lines + } + + fn push_replicate(line: &mut String, chr: char, times: usize) { + for _ in 0..times { + line.push(chr); + } + } + + fn visual_width(chr: char) -> usize { + if chr == '\t' { + 4 + } else { + 1 + } + } + + /// Write ~~~ to underline symbol + fn underline(&self, lineno_len: usize, lineno: u32, line: &str, into: &mut String) { + const NEWLINE_SIZE: usize = 1; + into.reserve(" | ".len() + lineno_len + line.len() + NEWLINE_SIZE); + + // Prefix + for _ in 0..lineno_len { + into.push(' '); + } + into.push_str(" | "); + + let mut pos = Position { + line: lineno, + character: 0, + }; + // Padding before underline + for chr in line.chars() { + if pos < self.range.start { + Self::push_replicate(into, ' ', Self::visual_width(chr)); + } else if pos < self.range.end { + Self::push_replicate(into, '~', Self::visual_width(chr)); + } else { + break; + } + pos.character += chr.len_utf16() as u32; + } + + if lineno == self.range.end.line { + while pos < self.range.end { + into.push('~'); + pos.character += 1; + } + } + + // Newline + into.push('\n'); + } + + fn code_context_from_contents( + &self, + contents: &Contents, + context_lines: u32, + ) -> (usize, String) { + let lines = self.get_line_context(context_lines, contents); + use pad::{Alignment, PadStr}; + // +1 since lines are shown with 1-index + let lineno_len = (self.range.start.line + context_lines + 1) + .to_string() + .len(); + + let mut result = String::new(); + + for (lineno, line) in lines.iter() { + let line = line.to_string(); + let line = line.trim_matches('\n'); + let lineno_str = (lineno + 1) + .to_string() + .pad_to_width_with_alignment(lineno_len, Alignment::Right); + let overlaps = self.range.start.line <= *lineno && *lineno <= self.range.end.line; + + if overlaps { + write!(result, "{lineno_str} --> ").unwrap(); + } else { + write!(result, "{lineno_str} | ").unwrap(); + } + + for chr in line.trim_end().chars() { + if chr == '\t' { + Self::push_replicate(&mut result, ' ', Self::visual_width(chr)); + } else { + result.push(chr); + } + } + result.push('\n'); + + if overlaps { + self.underline(lineno_len, *lineno, line, &mut result); + } + } + + (lineno_len, result) + } + + /// Create a string for pretty printing. + pub fn code_context(&self) -> String { + self.lineno_len_and_code_context().1 + } + + fn lineno_len_and_code_context(&self) -> (usize, String) { + let contents = self.source.contents(); + self.code_context_from_contents(&contents, Self::LINE_CONTEXT) + } + + pub fn show(&self, message: &str) -> String { + let (lineno_len, pretty_str) = self.lineno_len_and_code_context(); + let file_name = self.source.file_name(); + let mut result = String::new(); + + let lineno = self.range.start.line; + writeln!(result, "{}", &message).unwrap(); + for _ in 0..lineno_len { + result.push(' '); + } + writeln!( + result, + " --> {}:{}", + file_name.to_string_lossy(), + lineno + 1 + ) + .unwrap(); + for _ in 0..lineno_len { + result.push(' '); + } + writeln!(result, " |").unwrap(); + result.push_str(&pretty_str); + result + } + + /// Combines two lexical positions into a larger lexical position overlapping both. + /// The file name is assumed to be the same. + pub fn combine_into(self, other: &dyn AsRef) -> Self { + let other = other.as_ref(); + debug_assert!(self.source == other.source, "Assumes sources are equal"); + + let start = min(self.range.start, other.range.start); + let end = max(self.range.end, other.range.end); + + SrcPos { + source: self.source, + range: Range { start, end }, + } + } + + pub fn start(&self) -> Position { + self.range.start + } + + pub fn end(&self) -> Position { + self.range.end + } + + pub fn pos_at_end(&self) -> SrcPos { + SrcPos { + source: self.source.clone(), + range: Range::new(self.range.end, self.range.end), + } + } + + pub fn pos_at_beginning(&self) -> SrcPos { + SrcPos { + source: self.source.clone(), + range: Range::new(self.range.start, self.range.start), + } + } + + pub fn range(&self) -> Range { + self.range + } + + pub fn file_name(&self) -> &Path { + self.source.file_name() + } + + pub fn combine(&self, other: &dyn AsRef) -> Self { + self.clone().combine_into(other) + } + + pub fn contains(&self, pos: Position) -> bool { + self.range.contains(pos) + } + + pub fn end_pos(&self) -> SrcPos { + SrcPos::new(self.source.clone(), Range::new(self.end(), self.end())) + } +} + +/// Denotes an item with an associated source file. +/// +/// Most types that implement this trait do so through the blanket implementation +/// on [`HasSrcPos`](trait.HasSrcPos.html). +pub trait HasSource { + fn source(&self) -> &Source; +} + +impl HasSource for Source { + fn source(&self) -> &Source { + self + } +} + +/// Denotes an item with an associated lexical range in a source file. +pub trait HasSrcPos { + fn pos(&self) -> &SrcPos; +} + +impl HasSrcPos for SrcPos { + fn pos(&self) -> &SrcPos { + self + } +} + +impl HasSource for T { + fn source(&self) -> &Source { + &self.pos().source + } +} + +/// A wrapper around a PathBuf that ensures the path is absolute and simplified. +/// +/// This struct can be used similar to a [PathBuf], i.e., dereferencing it will return a [Path] +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub(crate) struct FilePath(PathBuf); + +impl std::ops::Deref for FilePath { + type Target = Path; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FilePath { + pub fn new(path: &Path) -> Self { + // In tests, when using inline files, paths are used that do not point to an existing file. + // In this case, we simply want to preserve the name without changing it. + if cfg!(test) && !path.exists() { + return Self(path.to_owned()); + } + // It would also be possible to use dunce::canonicalize here instead of path::absolute + // and dunce::simplify, but dunce::canonicalize resolves symlinks + // which we don't want (see issue #327) + let path = match std::path::absolute(path) { + // dunce::simplified converts UNC paths to regular paths. + // UNC paths have caused issues when a file was mounted on a network drive. + // Related issue: #115 + Ok(path) => dunce::simplified(&path).to_owned(), + Err(err) => { + // eprintln!( + // "Could not create absolute path {}: {:?}", + // path.to_string_lossy(), + // err + // ); + path.to_owned() + } + }; + Self(path) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::data::Latin1String; + use crate::syntax::test::{Code, CodeBuilder}; + use pretty_assertions::assert_eq; + + #[test] + fn srcpos_combine() { + let code = Code::new("hello world"); + + assert_eq!( + code.s1("hello").pos().combine(&code.s1("world").pos()), + code.pos() + ); + + assert_eq!(code.s1("h").pos().combine(&code.s1("d").pos()), code.pos()); + + assert_eq!(code.s1("d").pos().combine(&code.s1("h").pos()), code.pos()); + } + + fn with_code_from_file(contents: &str, fun: F) -> R + where + F: Fn(Code) -> R, + { + use std::io::Write; + let mut file = tempfile::NamedTempFile::new().unwrap(); + let file_name = file.path().to_owned(); + file.write_all(&Latin1String::from_utf8_unchecked(contents).bytes) + .unwrap(); + fun(CodeBuilder::new().code_from_source(Source::from_latin1_file(&file_name).unwrap())) + } + + #[test] + fn code_context_pos_from_filename() { + with_code_from_file("hello\nworld\n", |code: Code| { + assert_eq!( + code.s1("hello").pos().code_context(), + "\ +1 --> hello + | ~~~~~ +2 | world +" + ) + }); + } + + #[test] + fn code_context_pos_last_line_without_newline() { + let code = Code::new("hello world"); + let pos = code.s1("hello").pos(); + assert_eq!( + pos.code_context(), + "\ +1 --> hello world + | ~~~~~ +" + ); + } + + #[test] + fn code_context_pos_with_indent() { + let code = Code::new(" hello world"); + let pos = code.s1("hello").pos(); + assert_eq!( + pos.code_context(), + "\ +1 --> hello world + | ~~~~~ +" + ); + } + + #[test] + fn code_context_eof() { + let code = Code::new("h"); + assert_eq!( + code.eof_pos().code_context(), + "\ +1 --> h + | ~ +", + ); + } + + #[test] + fn code_context_eof_empty() { + let code = Code::new(""); + assert_eq!(code.eof_pos().code_context(), "1 --> \n | ~\n",); + } + + #[test] + fn code_context_with_context() { + let code = Code::new("hello\nworld"); + let pos = code.s1("hello").pos(); + assert_eq!( + pos.code_context(), + "\ +1 --> hello + | ~~~~~ +2 | world +", + ); + } + + #[test] + fn code_context_with_tabs() { + let code = Code::new("\thello\t"); + let pos = code.s1("hello\t").pos(); + assert_eq!( + pos.code_context(), + "\ +1 --> hello + | ~~~~~~~~~ +", + ); + } + + #[test] + fn code_context_non_ascii() { + let code = Code::new("åäö\nåäö\n__å_ä_ö__"); + let substr = code.s1("å_ä_ö"); + assert_eq!(substr.end().character - substr.start().character, 5); + assert_eq!( + substr.pos().code_context(), + "\ +1 | åäö +2 | åäö +3 --> __å_ä_ö__ + | ~~~~~ +", + ); + } + + #[test] + fn code_context_double_utf16() { + // Bomb emojii requires 2 utf-16 codes + let code = Code::new("\u{1F4A3}"); + assert_eq!(code.end().character - code.start().character, 2); + assert_eq!( + code.pos().code_context(), + "\ +1 --> \u{1F4A3} + | ~ +", + ); + } + + #[test] + fn code_context_non_ascii_from_file() { + with_code_from_file("åäö\nåäö\n__å_ä_ö__", |code: Code| { + let substr = code.s1("å_ä_ö"); + assert_eq!(substr.end().character - substr.start().character, 5); + assert_eq!( + substr.pos().code_context(), + "\ +1 | åäö +2 | åäö +3 --> __å_ä_ö__ + | ~~~~~ +", + ); + }); + } + + #[test] + fn code_context_with_full_context() { + let code = Code::new( + "\ +line1 +line2 +line3 +line4 +line5 +line6 +line7 +line8 +line9 +line10 +line11 +line12 +line13", + ); + let pos = code.s1("line10").pos(); + assert_eq!( + pos.code_context(), + " \ + 8 | line8 + 9 | line9 +10 --> line10 + | ~~~~~~ +11 | line11 +12 | line12 +", + ); + } + + #[test] + fn show_from_filename() { + with_code_from_file("hello\nworld\nline\n", |code: Code| { + assert_eq!( + code.s1("world").pos().show("Greetings"), + format!( + "\ +Greetings + --> {}:2 + | +1 | hello +2 --> world + | ~~~~~ +3 | line +", + code.source().file_name().to_string_lossy() + ) + ) + }); + } + + #[test] + fn show_contents() { + let code = Code::new("hello\nworld\nline\n"); + assert_eq!( + code.s1("world").pos().show("Greetings"), + format!( + "\ +Greetings + --> {}:2 + | +1 | hello +2 --> world + | ~~~~~ +3 | line +", + code.source().file_name().to_string_lossy() + ) + ); + } +} diff --git a/vhdl_lang/src/data/symbol_table.rs b/vhdl_lang/src/data/symbol_table.rs new file mode 100644 index 0000000..1c16076 --- /dev/null +++ b/vhdl_lang/src/data/symbol_table.rs @@ -0,0 +1,238 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com + +use super::latin_1::Latin1String; +use parking_lot::RwLock; +use std::sync::Arc; + +use fnv::FnvHashMap; + +/// Represents a unique string symbol. +/// +/// The `id` field can be used as a fast comparison key for symbols. +/// Two symbols are compared for equality based on VHDL's rules for identifiers: +/// * basic identifiers are compared case-insensitive (LRM 15.4.2) +/// * extended identifiers are compared case-sensitive (LRM 15.4.3) +#[derive(Clone, Debug, Eq)] +pub struct Symbol { + /// The unique ID of the symbol. + /// + /// Note: IDs are not necessarily contiguous. + pub(crate) id: usize, + + /// The name of the symbol + name: Arc, +} + +impl Symbol { + /// Creates a new symbol. The `id` parameter is assumed to be a valid + /// [`SymbolTable`](struct.SymbolTable.html) ID. + fn new(id: usize, name: &Arc) -> Symbol { + Symbol { + id, + name: Arc::clone(name), + } + } + + /// Returns the name of the symbol. + pub fn name(&self) -> &Latin1String { + self.name.as_ref() + } + + /// Returns the name of the symbol as a UTF-8 string. + pub fn name_utf8(&self) -> String { + self.name.to_string() + } +} + +impl PartialEq for Symbol { + /// Symbols are compared just based on the `id` field. + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl std::fmt::Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name_utf8()) + } +} + +impl std::hash::Hash for Symbol { + fn hash(&self, hasher: &mut H) { + self.id.hash(hasher); + } +} + +/// A thread-safe symbol table to keep track of identifiers. +/// +/// This table maintains a mapping from symbol strings to +/// [`Symbol`](struct.Symbol.html) objects. +/// Equivalent identifiers get identical IDs. +#[derive(Default)] +pub struct SymbolTable { + /// Symbol mapping. + /// + /// Basic identifiers containing upper-case letters are stored in verbatim + /// and normalized forms, with distinct [`Symbol`](struct.Symbol.html) objects, + /// but these objects contain the same ID. + name_to_symbol: RwLock, Symbol>>, +} + +impl SymbolTable { + pub fn insert_utf8(&self, name: &str) -> Symbol { + let name = Latin1String::from_utf8(name).unwrap(); + self.insert(&name) + } + + #[cfg(test)] + pub fn insert_extended_utf8(&self, name: &str) -> Symbol { + let name = Latin1String::from_utf8_unchecked(name); + self.insert_extended(&name) + } + + /// Looks up an identifier (basic or extended). + /// + /// Returns the corresponding `Symbol` instance if the identifier already exists, + /// and `None` otherwise. + pub fn lookup(&self, name: &Latin1String) -> Option { + let name_to_symbol = self.name_to_symbol.read(); + // Symbol already exists with identical case + name_to_symbol.get(name).cloned() + } + + /// Inserts a basic identifier and returns a corresponding `Symbol` instance. + pub fn insert(&self, name: &Latin1String) -> Symbol { + if let Some(symbol) = self.lookup(name) { + symbol + } else { + self.insert_new(name, false) + } + } + + /// Inserts an extended identifier and returns a corresponding `Symbol` instance. + pub fn insert_extended(&self, name: &Latin1String) -> Symbol { + if let Some(symbol) = self.lookup(name) { + symbol + } else { + self.insert_new(name, true) + } + } + + fn insert_new(&self, name: &Latin1String, is_extended: bool) -> Symbol { + let mut name_to_symbol = self.name_to_symbol.write(); + + // Lookup again after taking lock to avoid race-condition where new symbols are created in parallel + if let Some(sym) = name_to_symbol.get(name) { + // Symbol already exists with identical case + return sym.clone(); + } + + debug_assert_eq!(name.bytes.first() == Some(&b'\\'), is_extended); + let name = Arc::from(name.clone()); + if is_extended { + let id = name_to_symbol.len(); + let sym = Symbol::new(id, &name); + name_to_symbol.insert(name, sym.clone()); + return sym; + } + + // Symbol does not exists with the given case, try normalizing case + let normal_name = Arc::from(name.to_lowercase()); + + match name_to_symbol.get(&normal_name).cloned() { + // Symbol exists in normalized case + Some(normal_sym) => { + // Copy id from previous symbol + // Insert new symbol with given case and return it + let id = normal_sym.id; + let sym = Symbol::new(id, &name); + name_to_symbol.insert(name, sym.clone()); + sym + } + + // Symbol does not exist + None => { + // Create new id + let id = name_to_symbol.len(); + + if normal_name != name { + // If symbol is not already normalized case insert it + let sym = Symbol::new(id, &normal_name); + name_to_symbol.insert(normal_name, sym); + } + + let sym = Symbol::new(id, &name); + name_to_symbol.insert(name, sym.clone()); + sym + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn symbol_table_insert() { + let symtab = SymbolTable::default(); + let sym = symtab.insert_utf8("hello"); + assert_eq!(sym.name_utf8(), "hello"); + } + + #[test] + fn symbols_are_equal() { + let symtab = SymbolTable::default(); + let sym0 = symtab.insert_utf8("hello"); + let sym1 = symtab.insert_utf8("hello"); + assert_eq!(sym0, sym1); + assert_eq!(sym0.name_utf8(), "hello"); + assert_eq!(sym1.name_utf8(), "hello"); + + let sym0 = symtab.insert_utf8("Hello"); + let sym1 = symtab.insert_utf8("hello"); + assert_eq!(sym0, sym1); + assert_eq!(sym0.name_utf8(), "Hello"); + assert_eq!(sym1.name_utf8(), "hello"); + } + + #[test] + fn symbols_are_case_insensitive() { + let symtab = SymbolTable::default(); + let sym0 = symtab.insert_utf8("Hello"); + let sym1 = symtab.insert_utf8("hello"); + let sym2 = symtab.insert_utf8("heLLo"); + assert_eq!(sym0, sym1); + assert_eq!(sym0, sym2); + assert_eq!(sym1, sym2); + assert_eq!(sym0.name_utf8(), "Hello"); + assert_eq!(sym1.name_utf8(), "hello"); + assert_eq!(sym2.name_utf8(), "heLLo"); + } + + #[test] + fn extended_identifiers_symbols_are_case_sensitive() { + let symtab = SymbolTable::default(); + let sym0 = symtab.insert_extended_utf8("\\hello\\"); + let sym1 = symtab.insert_extended_utf8("\\HELLO\\"); + let sym2 = symtab.insert_extended_utf8("\\hello\\"); + assert_ne!(sym0, sym1); + assert_eq!(sym0, sym2); + assert_ne!(sym1, sym2); + assert_eq!(sym0.name_utf8(), "\\hello\\"); + assert_eq!(sym1.name_utf8(), "\\HELLO\\"); + assert_eq!(sym2.name_utf8(), "\\hello\\"); + } + + #[test] + fn symbols_are_not_equal() { + let symtab = SymbolTable::default(); + let sym0 = symtab.insert_utf8("hello"); + let sym1 = symtab.insert_utf8("abc"); + assert_ne!(sym0, sym1); + } +} diff --git a/vhdl_lang/src/formatting/architecture.rs b/vhdl_lang/src/formatting/architecture.rs new file mode 100644 index 0000000..ad520b9 --- /dev/null +++ b/vhdl_lang/src/formatting/architecture.rs @@ -0,0 +1,133 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com + +use crate::ast::ArchitectureBody; +use crate::formatting::buffer::Buffer; +use crate::formatting::VHDLFormatter; +use crate::{indented, HasTokenSpan, TokenSpan}; + +impl VHDLFormatter<'_> { + pub fn format_architecture(&self, arch: &ArchitectureBody, buffer: &mut Buffer) { + self.format_context_clause(&arch.context_clause, buffer); + if let Some(item) = arch.context_clause.last() { + self.line_break_preserve_whitespace(item.span().end_token, buffer); + } + let span = arch.span(); + // architecture of is + self.format_token_span(TokenSpan::new(span.start_token, arch.is_token()), buffer); + indented!(buffer, { self.format_declarations(&arch.decl, buffer) }); + buffer.line_break(); + self.format_token_id(arch.begin_token, buffer); + indented!(buffer, { + self.format_concurrent_statements(&arch.statements, buffer); + }); + buffer.line_break(); + // end [architecture] [name]; + self.format_token_span(TokenSpan::new(arch.end_token, span.end_token - 1), buffer); + self.format_token_id(span.end_token, buffer); + } +} + +#[cfg(test)] +mod test { + use crate::syntax::test::Code; + use vhdl_lang::formatting::test_utils::check_formatted; + + fn check_architecture_formatted(input: &str) { + check_formatted( + input, + input, + Code::architecture_body, + |formatter, arch, buffer| formatter.format_architecture(arch, buffer), + ) + } + + #[test] + fn format_empty_architecture() { + check_architecture_formatted( + "\ +architecture foo of bar is +begin +end foo;", + ); + + check_architecture_formatted( + "\ +architecture foo of bar is +begin +end architecture foo;", + ); + + check_architecture_formatted( + "\ +architecture foo of bar is +begin +end;", + ); + } + + #[test] + fn format_architecture_with_declarations() { + check_architecture_formatted( + "\ +architecture foo of bar is + constant x: foo := bar; +begin +end foo;", + ); + + check_architecture_formatted( + "\ +architecture foo of bar is + constant x: foo := bar; + signal y: bar := foobar; +begin +end foo;", + ); + } + + #[test] + fn format_full_architecture() { + check_architecture_formatted( + "\ +architecture foo of bar is + constant x: foo := bar; + signal y: bar := foobar; +begin + bar: process(clk) is + variable z: baz; + begin + if rising_edge(clk) then + if rst = '1' then + foo <= '0'; + else + foo <= bar and baz; + end if; + end if; + end process bar; + y <= x; -- An assignment +end foo;", + ); + } + + #[test] + fn format_architecture_preserve_whitespace() { + check_architecture_formatted( + "\ +architecture foo of bar is + constant x: foo := bar; + constant baz: char := '1'; + + signal y: bar := foobar; + signal foobar: std_logic := 'Z'; + + shared variable sv: natural; +begin +end foo;", + ); + } +} diff --git a/vhdl_lang/src/formatting/buffer.rs b/vhdl_lang/src/formatting/buffer.rs new file mode 100644 index 0000000..4353000 --- /dev/null +++ b/vhdl_lang/src/formatting/buffer.rs @@ -0,0 +1,330 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com + +use crate::syntax::{Comment, Value}; +use crate::{kind_str, Token}; +use std::cmp::max; +use std::iter; + +/// The Buffer is the (mostly) mutable object used to write tokens to a string. +/// It operates mostly on tokens and is capable of indenting, +/// de-indenting and keeping the indentation level. +pub struct Buffer { + inner: String, + /// insert an extra newline before pushing a token. + /// This is relevant when there is a trailing comment + insert_extra_newline: bool, + /// The current indentation level + indentation: usize, + /// The char used for indentation + indent_char: char, + /// The width used at each indentation level + indent_width: usize, +} + +impl Buffer { + pub fn new() -> Buffer { + Buffer { + inner: String::new(), + insert_extra_newline: false, + indentation: 0, + indent_char: ' ', + indent_width: 4, + } + } +} + +impl Default for Buffer { + fn default() -> Self { + Self::new() + } +} + +/// Returns whether a leading comment is on the same line as the token, i.e., +/// check the case +/// ```vhdl +/// /* some comment */ token +/// ``` +fn leading_comment_is_on_token_line(comment: &Comment, token: &Token) -> bool { + if !comment.multi_line { + return false; + } + if comment.range.start.line != comment.range.end.line { + return false; + } + token.pos.start().line == comment.range.start.line +} + +impl From for String { + fn from(value: Buffer) -> Self { + value.inner + } +} + +impl Buffer { + pub fn as_str(&self) -> &str { + self.inner.as_str() + } + + /// pushes a whitespace character to the buffer + pub fn push_whitespace(&mut self) { + if !self.insert_extra_newline { + self.push_ch(' '); + } + } + + fn format_comment(&mut self, comment: &Comment) { + if !comment.multi_line { + self.push_str("--"); + self.push_str(comment.value.trim_end()) + } else { + self.push_str("/*"); + self.push_str(&comment.value); + self.push_str("*/"); + } + } + + fn format_leading_comments(&mut self, comments: &[Comment]) { + for (i, comment) in comments.iter().enumerate() { + self.format_comment(comment); + if let Some(next_comment) = comments.get(i + 1) { + let number_of_line_breaks = + max(next_comment.range.start.line - comment.range.end.line, 1); + self.line_breaks(number_of_line_breaks); + } else { + self.line_break(); + } + } + } + + fn indent(&mut self) { + self.inner + .extend(iter::repeat(self.indent_char).take(self.indent_width * self.indentation)); + } + + /// Push a token to this buffer. + /// This takes care of all the leading and trailing comments attached to that token. + pub fn push_token(&mut self, token: &Token) { + if self.insert_extra_newline { + self.line_break(); + } + self.insert_extra_newline = false; + if let Some(comments) = &token.comments { + // This is for example the case for situations like + // some_token /* comment in between */ some_other token + if comments.leading.len() == 1 + && leading_comment_is_on_token_line(&comments.leading[0], token) + { + self.format_comment(&comments.leading[0]); + self.push_ch(' '); + } else if !comments.leading.is_empty() { + self.format_leading_comments(comments.leading.as_slice()); + } + } + match &token.value { + Value::Identifier(ident) => self.push_str(&ident.to_string()), + Value::String(string) => { + self.push_ch('"'); + for byte in &string.bytes { + if *byte == b'"' { + self.push_ch('"'); + self.push_ch('"'); + } else { + self.push_ch(*byte as char); + } + } + self.push_ch('"'); + } + Value::BitString(value, _) => self.push_str(&value.to_string()), + Value::AbstractLiteral(value, _) => self.push_str(&value.to_string()), + Value::Character(char) => { + self.push_ch('\''); + self.push_ch(*char as char); + self.push_ch('\''); + } + Value::Text(text) => self.push_str(&text.to_string()), + Value::None => self.push_str(kind_str(token.kind)), + } + if let Some(comments) = &token.comments { + if let Some(trailing_comment) = &comments.trailing { + self.push_ch(' '); + self.format_comment(trailing_comment); + self.insert_extra_newline = true + } + } + } + + fn push_str(&mut self, value: &str) { + self.inner.push_str(value); + } + + fn push_ch(&mut self, char: char) { + self.inner.push(char); + } + + /// Increase the indentation level. + /// After this call, all new-line pushes will be preceded by an indentation, + /// specified via the `indent_char` and `indent_width` properties. + /// + /// This call should always be matched with a `decrease_indent` call. + /// There is also the `indented` macro that combines the two calls. + pub fn increase_indent(&mut self) { + self.indentation += 1; + } + + pub fn decrease_indent(&mut self) { + self.indentation -= 1; + } + + /// Inserts a line break (i.e., newline) at the current position + pub fn line_break(&mut self) { + self.insert_extra_newline = false; + self.push_ch('\n'); + self.indent(); + } + + /// Inserts multiple line breaks. + /// Note that this method must always be used (i.e., is different from + /// multiple `line_break` calls) as this method only indents the last line break + pub fn line_breaks(&mut self, count: u32) { + self.insert_extra_newline = false; + for _ in 0..count { + self.push_ch('\n'); + } + self.indent(); + } +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::Code; + use crate::formatting::buffer::Buffer; + use std::iter::zip; + + fn check_token_formatted(input: &str, expected: &[&str]) { + let code = Code::new(input); + let tokens = code.tokenize(); + for (token, expected) in zip(tokens, expected) { + let mut buffer = Buffer::new(); + buffer.push_token(&token); + assert_eq!(buffer.as_str(), *expected); + } + } + + #[test] + fn format_simple_token() { + check_token_formatted("entity", &["entity"]); + check_token_formatted("foobar", &["foobar"]); + check_token_formatted("1 23 4E5 4e5", &["1", "23", "4E5", "4e5"]); + } + + #[test] + fn preserves_identifier_casing() { + check_token_formatted("FooBar foobar", &["FooBar", "foobar"]); + } + + #[test] + fn character_formatting() { + check_token_formatted("'a' 'Z' '''", &["'a'", "'Z'", "'''"]); + } + + #[test] + fn string_formatting() { + check_token_formatted( + r#""ABC" "" "DEF" """" "Hello "" ""#, + &["\"ABC\"", "\"\"", "\"DEF\"", "\"\"\"\"", "\"Hello \"\" \""], + ); + } + + #[test] + fn bit_string_formatting() { + check_token_formatted(r#"B"10" 20B"8" X"2F""#, &["B\"10\"", "20B\"8\"", "X\"2F\""]); + } + + #[test] + fn leading_comment() { + check_token_formatted( + "\ +-- I am a comment +foobar + ", + &["\ +-- I am a comment +foobar"], + ); + } + + #[test] + fn multiple_leading_comments() { + check_token_formatted( + "\ +-- I am a comment +-- So am I +foobar + ", + &["\ +-- I am a comment +-- So am I +foobar"], + ); + } + + #[test] + fn trailing_comments() { + check_token_formatted( + "\ +foobar --After foobar comes foobaz + ", + &["foobar --After foobar comes foobaz"], + ); + } + + #[test] + fn single_multiline_comment() { + check_token_formatted( + "\ +/** Some documentation. + * This is a token named 'entity' + */ +entity + ", + &["\ +/** Some documentation. + * This is a token named 'entity' + */ +entity"], + ); + } + + #[test] + fn multiline_comment_and_simple_comment() { + check_token_formatted( + "\ +/* I am a multiline comment */ +-- And I am a single line comment +entity + ", + &["\ +/* I am a multiline comment */ +-- And I am a single line comment +entity"], + ); + } + + #[test] + fn leading_comment_and_trailing_comment() { + check_token_formatted( + "\ +-- Leading comment +entity -- Trailing comment + ", + &["\ +-- Leading comment +entity -- Trailing comment"], + ); + } +} diff --git a/vhdl_lang/src/formatting/concurrent_statement.rs b/vhdl_lang/src/formatting/concurrent_statement.rs new file mode 100644 index 0000000..ac9bfc6 --- /dev/null +++ b/vhdl_lang/src/formatting/concurrent_statement.rs @@ -0,0 +1,928 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// This Source Code Form is subject to the terms of the Mozilla Public +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com + +use crate::ast::token_range::WithTokenSpan; +use crate::ast::{ + AssignmentRightHand, BlockStatement, CaseGenerateStatement, ConcurrentAssertStatement, + ConcurrentSignalAssignment, ConcurrentStatement, Conditionals, ElementAssociation, + ForGenerateStatement, GenerateBody, Ident, IfGenerateStatement, InstantiatedUnit, + InstantiationStatement, LabeledConcurrentStatement, ProcessStatement, SensitivityList, Target, + Waveform, WaveformElement, +}; +use crate::formatting::buffer::Buffer; +use crate::formatting::VHDLFormatter; +use crate::syntax::Kind; +use crate::{HasTokenSpan, TokenAccess}; +use vhdl_lang::ast::{Alternative, AssertStatement, ConcurrentProcedureCall}; +use vhdl_lang::{indented, TokenSpan}; + +impl VHDLFormatter<'_> { + pub fn join_on_newline( + &self, + items: &[T], + joiner: impl Fn(&Self, &T, &mut Buffer), + buffer: &mut Buffer, + ) { + for item in items { + buffer.line_break(); + joiner(self, item, buffer); + } + } + + pub fn format_concurrent_statements( + &self, + statements: &[LabeledConcurrentStatement], + buffer: &mut Buffer, + ) { + if statements.is_empty() { + return; + } + buffer.line_break(); + for (i, item) in statements.iter().enumerate() { + self.format_labeled_concurrent_statement(item, buffer); + if i < statements.len() - 1 { + self.line_break_preserve_whitespace(item.statement.get_end_token(), buffer); + } + } + } + + pub fn format_optional_label(&self, label: Option<&Ident>, buffer: &mut Buffer) { + if let Some(label) = label { + self.format_token_id(label.token, buffer); + // : + self.format_token_id(label.token + 1, buffer); + buffer.push_whitespace(); + } + } + + pub fn format_labeled_concurrent_statement( + &self, + statement: &LabeledConcurrentStatement, + buffer: &mut Buffer, + ) { + self.format_optional_label(statement.label.tree.as_ref(), buffer); + self.format_concurrent_statement(&statement.statement, buffer); + } + + pub fn format_concurrent_statement( + &self, + statement: &WithTokenSpan, + buffer: &mut Buffer, + ) { + use ConcurrentStatement::*; + let span = statement.span; + match &statement.item { + ProcedureCall(call) => self.format_procedure_call(call, span, buffer), + Block(block) => self.format_block_statement(block, span, buffer), + Process(process) => self.format_process_statement(process, span, buffer), + Assert(assert) => self.format_concurrent_assert_statement(assert, span, buffer), + Assignment(assignment) => self.format_assignment_statement(assignment, span, buffer), + Instance(instantiation_statement) => { + self.format_instantiation_statement(instantiation_statement, span, buffer) + } + ForGenerate(for_generate) => { + self.format_for_generate_statement(for_generate, span, buffer) + } + IfGenerate(if_generate) => self.format_if_generate_statement(if_generate, span, buffer), + CaseGenerate(case_generate) => { + self.format_case_generate_statement(case_generate, span, buffer) + } + } + } + + pub fn format_procedure_call( + &self, + call: &ConcurrentProcedureCall, + span: TokenSpan, + buffer: &mut Buffer, + ) { + if call.postponed { + self.format_token_id(span.start_token, buffer); + buffer.push_whitespace(); + } + self.format_call_or_indexed(&call.call.item, call.call.span, buffer); + // ; + self.format_token_id(span.end_token, buffer); + } + + pub fn format_block_statement( + &self, + block: &BlockStatement, + span: TokenSpan, + buffer: &mut Buffer, + ) { + // block + self.format_token_id(span.start_token, buffer); + if let Some(guard_condition) = &block.guard_condition { + self.format_token_id(guard_condition.span.start_token - 1, buffer); + self.format_expression(guard_condition.as_ref(), buffer); + self.format_token_id(guard_condition.span.end_token + 1, buffer); + } + if let Some(is_token) = block.is_token { + buffer.push_whitespace(); + self.format_token_id(is_token, buffer); + } + indented!(buffer, { + if let Some(generic_clause) = &block.header.generic_clause { + buffer.line_break(); + self.format_interface_list(generic_clause, buffer); + } + if let Some(generic_map) = &block.header.generic_map { + buffer.line_break(); + self.format_map_aspect(generic_map, buffer); + // ; + self.format_token_id(generic_map.span.end_token + 1, buffer); + } + if let Some(port_clause) = &block.header.port_clause { + buffer.line_break(); + self.format_interface_list(port_clause, buffer); + } + if let Some(port_map) = &block.header.port_map { + buffer.line_break(); + self.format_map_aspect(port_map, buffer); + // ; + self.format_token_id(port_map.span.end_token + 1, buffer); + } + self.format_declarations(&block.decl, buffer); + }); + buffer.line_break(); + self.format_token_id(block.begin_token, buffer); + buffer.line_break(); + indented!(buffer, { + self.format_concurrent_statements(&block.statements, buffer) + }); + self.format_token_span( + TokenSpan::new(block.end_token, block.span.end_token - 1), + buffer, + ); + // ; + self.format_token_id(block.span.end_token, buffer); + } + + pub fn format_process_statement( + &self, + process: &ProcessStatement, + span: TokenSpan, + buffer: &mut Buffer, + ) { + self.token_with_opt_postponed(span, buffer); + if let Some(sensitivity_list) = &process.sensitivity_list { + match &sensitivity_list.item { + SensitivityList::Names(names) => { + self.format_token_id(sensitivity_list.span.start_token, buffer); + self.format_name_list(buffer, names); + self.format_token_id(sensitivity_list.span.end_token, buffer); + } + SensitivityList::All => self.join_token_span(sensitivity_list.span, buffer), + } + } + if let Some(is_token) = process.is_token { + buffer.push_whitespace(); + self.format_token_id(is_token, buffer); + } + indented!(buffer, { self.format_declarations(&process.decl, buffer) }); + buffer.line_break(); + self.format_token_id(process.begin_token, buffer); + self.format_sequential_statements(&process.statements, buffer); + buffer.line_break(); + self.format_token_span( + TokenSpan::new(process.end_token, process.span.end_token - 1), + buffer, + ); + // ; + self.format_token_id(process.span.end_token, buffer); + } + + fn token_with_opt_postponed(&self, span: TokenSpan, buffer: &mut Buffer) { + if self.tokens.index(span.start_token).kind == Kind::Postponed { + // postponed + self.format_token_span( + TokenSpan::new(span.start_token, span.start_token + 1), + buffer, + ); + } else { + // + self.format_token_id(span.start_token, buffer); + } + } + + pub fn format_concurrent_assert_statement( + &self, + statement: &ConcurrentAssertStatement, + span: TokenSpan, + buffer: &mut Buffer, + ) { + self.token_with_opt_postponed(span, buffer); + buffer.push_whitespace(); + self.format_assert_statement(&statement.statement, buffer); + // ; + self.format_token_id(span.end_token, buffer); + } + + pub fn format_assert_statement(&self, assert_statement: &AssertStatement, buffer: &mut Buffer) { + self.format_expression(assert_statement.condition.as_ref(), buffer); + if let Some(report) = &assert_statement.report { + buffer.push_whitespace(); + self.format_token_id(report.span.start_token - 1, buffer); + buffer.push_whitespace(); + self.format_expression(report.as_ref(), buffer); + } + self.format_opt_severity(assert_statement.severity.as_ref(), buffer); + } + + pub fn format_assignment_statement( + &self, + assignment_statement: &ConcurrentSignalAssignment, + span: TokenSpan, + buffer: &mut Buffer, + ) { + if let AssignmentRightHand::Selected(selected) = &assignment_statement.assignment.rhs { + // with + self.format_token_id(selected.expression.span.start_token - 1, buffer); + buffer.push_whitespace(); + self.format_expression(selected.expression.as_ref(), buffer); + buffer.push_whitespace(); + // select + self.format_token_id(selected.expression.span.end_token + 1, buffer); + buffer.push_whitespace(); + } + self.format_target(&assignment_statement.assignment.target, buffer); + buffer.push_whitespace(); + // <= + self.format_token_id( + assignment_statement.assignment.target.span.end_token + 1, + buffer, + ); + buffer.push_whitespace(); + if let Some(mechanism) = &assignment_statement.assignment.delay_mechanism { + self.format_delay_mechanism(mechanism, buffer); + buffer.push_whitespace(); + } + self.format_assignment_right_hand( + &assignment_statement.assignment.rhs, + Self::format_waveform, + buffer, + ); + self.format_token_id(span.end_token, buffer); + } + + pub fn format_assignment_right_hand( + &self, + right_hand: &AssignmentRightHand, + formatter: impl Fn(&Self, &T, &mut Buffer), + buffer: &mut Buffer, + ) { + use AssignmentRightHand::*; + match right_hand { + Simple(simple) => formatter(self, simple, buffer), + Conditional(conditionals) => { + self.format_assignment_right_hand_conditionals(conditionals, formatter, buffer) + } + Selected(selection) => { + for alternative in &selection.alternatives { + self.format_alternative(alternative, &formatter, buffer); + if self + .tokens + .get_token(alternative.span.end_token + 1) + .is_some_and(|token| token.kind == Kind::Comma) + { + self.format_token_id(alternative.span.end_token + 1, buffer); + buffer.push_whitespace(); + } + } + } + } + } + + pub fn format_alternative( + &self, + alternative: &Alternative, + formatter: &impl Fn(&Self, &T, &mut Buffer), + buffer: &mut Buffer, + ) { + formatter(self, &alternative.item, buffer); + buffer.push_whitespace(); + for (i, choice) in alternative.choices.iter().enumerate() { + if i == 0 { + // when + self.format_token_id(choice.span.start_token - 1, buffer); + buffer.push_whitespace(); + } + self.format_choice(choice, buffer); + if i < alternative.choices.len() - 1 { + buffer.push_whitespace(); + // | + self.format_token_id(choice.span.end_token + 1, buffer); + buffer.push_whitespace(); + } + } + } + + pub fn format_assignment_right_hand_conditionals( + &self, + conditionals: &Conditionals, + formatter: impl Fn(&Self, &T, &mut Buffer), + buffer: &mut Buffer, + ) { + for cond in &conditionals.conditionals { + // item + formatter(self, &cond.item, buffer); + let condition = &cond.condition; + buffer.push_whitespace(); + // when + self.format_token_id(condition.span.start_token - 1, buffer); + buffer.push_whitespace(); + self.format_expression(condition.as_ref(), buffer); + // [else] + if self + .tokens + .get_token(cond.condition.span.end_token + 1) + .is_some_and(|token| token.kind == Kind::Else) + { + buffer.push_whitespace(); + self.format_token_id(cond.condition.span.end_token + 1, buffer); + buffer.push_whitespace(); + } + } + if let Some((statements, _)) = &conditionals.else_item { + // else handled above + formatter(self, statements, buffer); + } + } + + pub fn format_waveform(&self, waveform: &Waveform, buffer: &mut Buffer) { + match waveform { + Waveform::Elements(elements) => { + for (i, element) in elements.iter().enumerate() { + self.format_waveform_element(element, buffer); + if i < elements.len() - 1 { + self.format_token_id(element.get_end_token() + 1, buffer); + buffer.push_whitespace(); + } + } + } + Waveform::Unaffected(token) => self.format_token_id(*token, buffer), + } + } + + pub fn format_waveform_element(&self, element: &WaveformElement, buffer: &mut Buffer) { + self.format_expression(element.value.as_ref(), buffer); + if let Some(after) = &element.after { + buffer.push_whitespace(); + self.format_token_id(after.get_start_token() - 1, buffer); + buffer.push_whitespace(); + self.format_expression(after.as_ref(), buffer); + } + } + + pub fn format_target(&self, target: &WithTokenSpan, buffer: &mut Buffer) { + match &target.item { + Target::Name(name) => self.format_name(WithTokenSpan::new(name, target.span), buffer), + Target::Aggregate(associations) => { + self.format_target_aggregate(associations, target.span, buffer) + } + } + } + + pub fn format_target_aggregate( + &self, + associations: &[WithTokenSpan], + span: TokenSpan, + buffer: &mut Buffer, + ) { + // ( + self.format_token_id(span.start_token, buffer); + self.format_element_associations(associations, buffer); + // ) + self.format_token_id(span.end_token, buffer); + } + + pub fn format_instantiation_statement( + &self, + statement: &InstantiationStatement, + span: TokenSpan, + buffer: &mut Buffer, + ) { + if matches!( + self.tokens.index(span.start_token).kind, + Kind::Component | Kind::Entity | Kind::Configuration + ) { + self.format_token_id(span.start_token, buffer); + buffer.push_whitespace(); + } + match &statement.unit { + InstantiatedUnit::Component(name) | InstantiatedUnit::Configuration(name) => { + self.format_name(name.as_ref(), buffer); + } + InstantiatedUnit::Entity(name, architecture) => { + self.format_name(name.as_ref(), buffer); + if let Some(arch) = architecture { + self.join_token_span( + TokenSpan::new(arch.item.token - 1, arch.item.token + 1), + buffer, + ) + } + } + } + if let Some(generic_map) = &statement.generic_map { + indented!(buffer, { + buffer.line_break(); + self.format_map_aspect(generic_map, buffer); + }); + } + if let Some(port_map) = &statement.port_map { + indented!(buffer, { + buffer.line_break(); + self.format_map_aspect(port_map, buffer); + }); + } + self.format_token_id(span.end_token, buffer); + } + + pub fn format_for_generate_statement( + &self, + statement: &ForGenerateStatement, + span: TokenSpan, + buffer: &mut Buffer, + ) { + // for + self.format_token_id(span.start_token, buffer); + buffer.push_whitespace(); + // index + self.format_ident(&statement.index_name, buffer); + buffer.push_whitespace(); + // in + self.format_token_id(statement.index_name.tree.token + 1, buffer); + buffer.push_whitespace(); + self.format_discrete_range(&statement.discrete_range, buffer); + buffer.push_whitespace(); + self.format_token_id(statement.generate_token, buffer); + self.format_generate_body(&statement.body, buffer); + buffer.line_break(); + self.format_token_span( + TokenSpan::new(statement.end_token, span.end_token - 1), + buffer, + ); + self.format_token_id(span.end_token, buffer); + } + + pub fn format_if_generate_statement( + &self, + statement: &IfGenerateStatement, + span: TokenSpan, + buffer: &mut Buffer, + ) { + for cond in &statement.conds.conditionals { + let condition = &cond.condition; + if let Some(label) = &cond.item.alternative_label { + // if | elsif + self.format_token_id(label.tree.token - 1, buffer); + buffer.push_whitespace(); + // label + self.format_token_id(label.tree.token, buffer); + // : + self.format_token_id(label.tree.token + 1, buffer); + buffer.push_whitespace(); + } else { + self.format_token_id(condition.span.start_token - 1, buffer); + buffer.push_whitespace(); + } + self.format_expression(condition.as_ref(), buffer); + buffer.push_whitespace(); + // generate + self.format_token_id(condition.span.end_token + 1, buffer); + self.format_generate_body(&cond.item, buffer); + buffer.line_break(); + } + if let Some((statements, token)) = &statement.conds.else_item { + if let Some(label) = &statements.alternative_label { + // else + self.format_token_id(label.tree.token - 1, buffer); + buffer.push_whitespace(); + // label + self.format_token_id(label.tree.token, buffer); + // : + self.format_token_id(label.tree.token + 1, buffer); + buffer.push_whitespace(); + // generate + self.format_token_id(label.tree.token + 2, buffer); + } else { + // else + self.format_token_id(*token, buffer); + buffer.push_whitespace(); + // generate + self.format_token_id(*token + 1, buffer); + } + self.format_generate_body(statements, buffer); + buffer.line_break(); + } + if statement.end_label_pos.is_some() { + // end if