commit 8593beff8c21bd3d3e417ac893b9f317f5386070 Author: LSTM-Kirigaya <1193466151@qq.com> Date: Fri Oct 4 23:29:34 2024 +0800 first commit 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