diff --git a/Pipfile.lock b/Pipfile.lock index 2498c5763..493e15bad 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,10 +18,10 @@ "default": { "appdirs": { "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" ], - "version": "==1.4.3" + "version": "==1.4.4" }, "astor": { "hashes": [ @@ -52,11 +52,11 @@ }, "click": { "hashes": [ - "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", - "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], "index": "pypi", - "version": "==7.1.1" + "version": "==7.1.2" }, "dill": { "hashes": [ @@ -93,25 +93,25 @@ }, "importlib-resources": { "hashes": [ - "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2", - "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8" + "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca", + "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49" ], "markers": "python_version < '3.7'", - "version": "==1.4.0" + "version": "==1.5.0" }, "more-itertools": { "hashes": [ - "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", - "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" + "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", + "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" ], - "version": "==8.2.0" + "version": "==8.3.0" }, "packaging": { "hashes": [ - "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", - "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], - "version": "==20.3" + "version": "==20.4" }, "pluggy": { "hashes": [ @@ -146,10 +146,10 @@ }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492", + "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a" ], - "version": "==2.4.6" + "version": "==3.0.0a1" }, "pyrsistent": { "hashes": [ @@ -160,11 +160,11 @@ }, "pytest": { "hashes": [ - "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", - "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" + "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", + "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" ], "index": "pypi", - "version": "==5.4.1" + "version": "==5.4.2" }, "python-dateutil": { "hashes": [ @@ -176,10 +176,10 @@ }, "six": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.14.0" + "version": "==1.15.0" }, "tabulate": { "hashes": [ @@ -190,24 +190,24 @@ }, "toml": { "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" ], - "version": "==0.10.0" + "version": "==0.10.1" }, "tox": { "hashes": [ - "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303", - "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471" + "sha256:322dfdf007d7d53323f767badcb068a5cfa7c44d8aabb698d131b28cf44e62c4", + "sha256:8c9ad9b48659d291c5bc78bcabaa4d680d627687154b812fa52baedaa94f9f83" ], - "version": "==3.14.6" + "version": "==3.15.1" }, "virtualenv": { "hashes": [ - "sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25", - "sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5" + "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf", + "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70" ], - "version": "==20.0.15" + "version": "==20.0.21" }, "wcwidth": { "hashes": [ @@ -235,10 +235,10 @@ }, "appdirs": { "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" ], - "version": "==1.4.3" + "version": "==1.4.4" }, "attrs": { "hashes": [ @@ -265,17 +265,17 @@ }, "bleach": { "hashes": [ - "sha256:cc8da25076a1fe56c3ac63671e2194458e0c4d9c7becfd52ca251650d517903c", - "sha256:e78e426105ac07026ba098f04de8abe9b6e3e98b5befbf89b51a5ef0a4292b03" + "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f", + "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b" ], - "version": "==3.1.4" + "version": "==3.1.5" }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -286,11 +286,11 @@ }, "click": { "hashes": [ - "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", - "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], "index": "pypi", - "version": "==7.1.1" + "version": "==7.1.2" }, "distlib": { "hashes": [ @@ -337,11 +337,11 @@ }, "importlib-resources": { "hashes": [ - "sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2", - "sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8" + "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca", + "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49" ], "markers": "python_version < '3.7'", - "version": "==1.4.0" + "version": "==1.5.0" }, "isort": { "hashes": [ @@ -360,69 +360,58 @@ }, "keyring": { "hashes": [ - "sha256:197fd5903901030ef7b82fe247f43cfed2c157a28e7747d1cfcf4bc5e699dd03", - "sha256:8179b1cdcdcbc221456b5b74e6b7cfa06f8dd9f239eb81892166d9223d82c5ba" + "sha256:3401234209015144a5d75701e71cb47239e552b0882313e9f51e8976f9e27843", + "sha256:c53e0e5ccde3ad34284a40ce7976b5b3a3d6de70344c3f8ee44364cc340976ec" ], - "version": "==21.2.0" + "version": "==21.2.1" }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "version": "==1.1.1" + "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", + "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", + "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", + "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", + "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", + "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", + "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", + "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", + "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", + "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", + "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", + "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", + "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", + "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", + "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", + "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", + "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", + "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", + "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", + "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", + "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", + "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" + ], + "version": "==2.0.0a1" }, "more-itertools": { "hashes": [ - "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", - "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" + "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", + "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" ], - "version": "==8.2.0" + "version": "==8.3.0" }, "packaging": { "hashes": [ - "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", - "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], - "version": "==20.3" + "version": "==20.4" }, "pathspec": { "hashes": [ - "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", - "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" + "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", + "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" ], - "version": "==0.7.0" + "version": "==0.8.0" }, "pkginfo": { "hashes": [ @@ -455,18 +444,18 @@ }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492", + "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a" ], - "version": "==2.4.6" + "version": "==3.0.0a1" }, "pytest": { "hashes": [ - "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", - "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" + "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", + "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" ], "index": "pypi", - "version": "==5.4.1" + "version": "==5.4.2" }, "pytest-pycharm": { "hashes": [ @@ -478,43 +467,43 @@ }, "pytz": { "hashes": [ - "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", - "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" ], - "version": "==2019.3" + "version": "==2020.1" }, "readme-renderer": { "hashes": [ - "sha256:1b6d8dd1673a0b293766b4106af766b6eff3654605f9c4f239e65de6076bc222", - "sha256:e67d64242f0174a63c3b727801a2fff4c1f38ebe5d71d95ff7ece081945a6cd4" + "sha256:cbe9db71defedd2428a1589cdc545f9bd98e59297449f69d721ef8f1cfced68d", + "sha256:cc4957a803106e820d05d14f71033092537a22daa4f406dfbdd61177e0936376" ], - "version": "==25.0" + "version": "==26.0" }, "regex": { "hashes": [ - "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431", - "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242", - "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1", - "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d", - "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045", - "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b", - "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400", - "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa", - "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0", - "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69", - "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74", - "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb", - "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26", - "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5", - "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2", - "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce", - "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab", - "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e", - "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70", - "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc", - "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0" - ], - "version": "==2020.2.20" + "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927", + "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561", + "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3", + "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe", + "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c", + "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad", + "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1", + "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108", + "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929", + "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4", + "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994", + "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4", + "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd", + "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577", + "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7", + "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5", + "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f", + "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a", + "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd", + "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e", + "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01" + ], + "version": "==2020.5.14" }, "requests": { "hashes": [ @@ -532,10 +521,10 @@ }, "six": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.14.0" + "version": "==1.15.0" }, "snowballstemmer": { "hashes": [ @@ -546,19 +535,19 @@ }, "sphinx": { "hashes": [ - "sha256:588ab849305802b0d68e550c9fb7de25555e8b0ce82ceaa5c2826aa518e97a75", - "sha256:5f8c74f548865d7ea0cd3c496da98d41ec822f229445281017630d290b8c0851" + "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572", + "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83" ], "index": "pypi", - "version": "==3.0.0b1" + "version": "==3.0.3" }, "sphinx-rtd-theme": { "hashes": [ - "sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4", - "sha256:728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a" + "sha256:1ba9bbc8898ed8531ac8d140b4ff286d57010fb878303b2efae3303726ec821b", + "sha256:a18194ae459f6a59b0d56e4a8b4c576c0125fb9a12f2211e652b4a8133092e14" ], "index": "pypi", - "version": "==0.4.3" + "version": "==0.5.0rc1" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -604,17 +593,17 @@ }, "toml": { "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" ], - "version": "==0.10.0" + "version": "==0.10.1" }, "tox": { "hashes": [ - "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303", - "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471" + "sha256:322dfdf007d7d53323f767badcb068a5cfa7c44d8aabb698d131b28cf44e62c4", + "sha256:8c9ad9b48659d291c5bc78bcabaa4d680d627687154b812fa52baedaa94f9f83" ], - "version": "==3.14.6" + "version": "==3.15.1" }, "tox-pyenv": { "hashes": [ @@ -626,10 +615,10 @@ }, "tqdm": { "hashes": [ - "sha256:03d2366c64d44c7f61e74c700d9b202d57e9efe355ea5c28814c52bfe7a50b8c", - "sha256:be5ddeec77d78ba781ea41eacb2358a77f74cc2407f54b82222d7ee7dc8c8ccf" + "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e", + "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f" ], - "version": "==4.44.1" + "version": "==4.46.0" }, "twine": { "hashes": [ @@ -667,17 +656,17 @@ }, "urllib3": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==1.25.8" + "version": "==1.25.9" }, "virtualenv": { "hashes": [ - "sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25", - "sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5" + "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf", + "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70" ], - "version": "==20.0.15" + "version": "==20.0.21" }, "wcwidth": { "hashes": [ diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index 299b614ea..772191361 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -4544,7 +4544,7 @@ ;;;;;;;;;;;;;;;; (defn ^:private collect-methods - "Collect method and interface declarations for `deftype` and `defrecord` + "Collect method and interface declarations for `deftype`, `defrecord`, and `reify` into a map containing `:interfaces` and `:methods` keys." [method-impls] (group-by (fn [v] @@ -4574,18 +4574,28 @@ Interface or protocol implementations are declared as the name of the interface or protocol as a symbol, followed by 1 or more method - definitions for that interface. Types are not required to declare - any interface implementations. Types which do declare interface - implementations are required to implement all interface methods. Failing - to implement all interface methods is a compile time error. Types - implementing `object` are not required to implement all `object` methods. + definitions for that interface. Types are not required to declare any + interface implementations. Types which do declare interface implementations + are required to implement all interface methods. Failing to implement all + interface methods is a compile time error. Types implementing `object` are + not required to implement all `object` methods. Method declarations should appear as: + (method-name [arg1] & body) (method-name [arg1 arg2 ...] & body) - Unlike in Clojure, interface and protocol methods are permitted to - include variadic arguments. + Unlike in Clojure, interface and protocol methods are permitted to include + variadic arguments. Single-arity methods may also declare support for + keyword arguments using either the `:apply` or `:collect` strategy on the + `:kwargs` metadata on the method name. Methods may be declared as static + (using the `:staticmethod`) or class methods (using the `:classmethod`) + metadata on the the name. Both static methods and class methods can be + declared with multiple arities or may be defined with a single arity and + keyword arguments. Finally, single-arity methods may be declared as Python + properties using the `:property` metadata on the method name. These facilities + are provided as a means to interoperate with Python code and their use is + discouraged in pure Basilisp code. Type objects are created with sensible `object` defaults as by `attrs`. New types may override `object` defaults. An `__init__` function is @@ -4612,6 +4622,57 @@ (def ~ctor-name ~type-name) ~type-name))) +(defmacro reify + "Create a new Python object of an anonymous type which implements 0 or more + Python interfaces or Basilisp protocols. + + Unlike types created via `deftype`, `reify` returns an object which implements + the named interfaces using the implementations provided. You may not provide + fields as such to `reify`, though `reify` closes over any local names defined + in the same lexical context. These fields may serve as private fields of the + created object. + + Interface or protocol implementations are declared as the name of the + interface or protocol as a symbol, followed by 1 or more method + definitions for that interface. Types are not required to declare any + interface implementations. Types which do declare interface implementations + are required to implement all interface methods. Failing to implement all + interface methods is a compile time error. Types implementing `object` are + not required to implement all `object` methods. + + Method declarations should appear as: + + (method-name [arg1] & body) + (method-name [arg1 arg2 ...] & body) + + Unlike in Clojure, interface and protocol methods are permitted to + include variadic arguments. Single-arity methods may also declare support for + keyword arguments using either the `:apply` or `:collect` strategy on the + `:kwargs` metadata on the method name. Finally, single-arity methods may be + declared as Python properties using the `:property` metadata on the method + name. These facilities are provided as a means to interoperate with Python + code and their use is discouraged in pure Basilisp code. + + `reify` does not support class method or static method members and attempting + to declare `reify` members as class or static members will result in a + compile time error. + + Type objects are created with sensible `object` defaults as by `attrs`. + New types may override `object` defaults. + + Reified objects always implement `basilisp.lang.interfaces/IWithMeta` and + transfer the metadata from the form to the created object. + + Methods must supply a `this` or `self` parameter. `recur` special forms + used in the body of a method should not include that parameter, as it + will be supplied automatically." + [& method-impls] + (let [{:keys [interfaces methods]} (collect-methods method-impls)] + (with-meta + `(reify* :implements [~@interfaces python/object] + ~@methods) + (meta &form)))) + ;;;;;;;;;;;;; ;; Records ;; ;;;;;;;;;;;;; @@ -4677,14 +4738,15 @@ Interface or protocol implementations are declared as the name of the interface or protocol as a symbol, followed by 1 or more method - definitions for that interface. Records are not required to declare - any interface implementations. Records which do declare interface - implementations are required to implement all interface methods. Failing - to implement all interface methods is a compile time error. Records - implementing `object` are not required to implement all `object` methods. + definitions for that interface. Types are not required to declare any + interface implementations. Types which do declare interface implementations + are required to implement all interface methods. Failing to implement all + interface methods is a compile time error. Types implementing `object` are + not required to implement all `object` methods. Method declarations should appear as: + (method-name [arg1] & body) (method-name [arg1 arg2 ...] & body) Records objects are created with sensible `object` defaults as by `attrs`. diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index dc2b730cd..dcede23a0 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -114,6 +114,7 @@ PyTuple, Quote, Recur, + Reify, Require, RequireAlias, Set as SetNode, @@ -964,9 +965,9 @@ def _def_ast( # pylint: disable=too-many-branches,too-many-locals def __deftype_method_param_bindings( - ctx: AnalyzerContext, params: vec.Vector + ctx: AnalyzerContext, params: vec.Vector, special_form: sym.Symbol ) -> Tuple[bool, int, List[Binding]]: - """Generate parameter bindings for deftype* methods. + """Generate parameter bindings for `deftype*` or `reify*` methods. Return a tuple containing a boolean, indicating if the parameter bindings contain a variadic binding, an integer indicating the fixed arity of the @@ -975,12 +976,14 @@ def __deftype_method_param_bindings( Special cases for individual method types must be handled by their respective handlers. This method will only produce vanilla ARG type bindings.""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + has_vargs, vargs_idx = False, 0 param_nodes = [] for i, s in enumerate(params): if not isinstance(s, sym.Symbol): raise AnalyzerException( - "deftype* method parameter name must be a symbol", form=s + f"{special_form} method parameter name must be a symbol", form=s ) if s == AMPERSAND: @@ -1007,7 +1010,7 @@ def __deftype_method_param_bindings( if not isinstance(vargs_sym, sym.Symbol): raise AnalyzerException( - "deftype* method rest parameter name must be a symbol", + f"{special_form} method rest parameter name must be a symbol", form=vargs_sym, ) @@ -1036,7 +1039,7 @@ def __deftype_classmethod( args: vec.Vector, kwarg_support: Optional[KeywordArgSupport] = None, ) -> DefTypeClassMethodArity: - """Emit a node for a :classmethod member of a deftype* form.""" + """Emit a node for a :classmethod member of a `deftype*` form.""" with ctx.hide_parent_symbol_table(), ctx.new_symbol_table( method_name, is_context_boundary=True ): @@ -1044,12 +1047,12 @@ def __deftype_classmethod( cls_arg = args[0] except IndexError: raise AnalyzerException( - f"deftype* class method must include 'cls' argument", form=args + "deftype* class method must include 'cls' argument", form=args ) else: if not isinstance(cls_arg, sym.Symbol): raise AnalyzerException( - f"deftype* class method 'cls' argument must be a symbol", form=args + "deftype* class method 'cls' argument must be a symbol", form=args, ) cls_binding = Binding( form=cls_arg, @@ -1061,7 +1064,7 @@ def __deftype_classmethod( params = args[1:] has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings( - ctx, params + ctx, params, SpecialForm.DEFTYPE ) with ctx.new_func_ctx(FunctionContext.CLASSMETHOD), ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) @@ -1088,25 +1091,29 @@ def __deftype_classmethod( return method -def __deftype_method( +def __deftype_or_reify_method( # pylint: disable=too-many-arguments,too-many-locals ctx: AnalyzerContext, form: Union[llist.List, ISeq], method_name: str, args: vec.Vector, + special_form: sym.Symbol, kwarg_support: Optional[KeywordArgSupport] = None, ) -> DefTypeMethodArity: - """Emit a node for a method member of a deftype* form.""" + """Emit a node for a method member of a `deftype*` or `reify*` form.""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + with ctx.new_symbol_table(method_name, is_context_boundary=True): try: this_arg = args[0] except IndexError: raise AnalyzerException( - f"deftype* method must include 'this' or 'self' argument", form=args + f"{special_form} method must include 'this' or 'self' argument", + form=args, ) else: if not isinstance(this_arg, sym.Symbol): raise AnalyzerException( - f"deftype* method 'this' argument must be a symbol", form=args + f"{special_form} method 'this' argument must be a symbol", form=args ) this_binding = Binding( form=this_arg, @@ -1118,7 +1125,7 @@ def __deftype_method( params = args[1:] has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings( - ctx, params + ctx, params, special_form ) loop_id = genname(method_name) @@ -1149,24 +1156,29 @@ def __deftype_method( return method -def __deftype_property( +def __deftype_or_reify_property( ctx: AnalyzerContext, form: Union[llist.List, ISeq], method_name: str, args: vec.Vector, + special_form: sym.Symbol, ) -> DefTypeProperty: - """Emit a node for a :property member of a deftype* form.""" + """Emit a node for a :property member of a `deftype*` or `reify*` form.""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + with ctx.new_symbol_table(method_name, is_context_boundary=True): try: this_arg = args[0] except IndexError: raise AnalyzerException( - f"deftype* property must include 'this' or 'self' argument", form=args + f"{special_form} property must include 'this' or 'self' argument", + form=args, ) else: if not isinstance(this_arg, sym.Symbol): raise AnalyzerException( - f"deftype* property 'this' argument must be a symbol", form=args + f"{special_form} property 'this' argument must be a symbol", + form=args, ) this_binding = Binding( form=this_arg, @@ -1177,14 +1189,16 @@ def __deftype_property( ctx.put_new_symbol(this_arg, this_binding, warn_if_unused=False) params = args[1:] - has_vargs, _, param_nodes = __deftype_method_param_bindings(ctx, params) + has_vargs, _, param_nodes = __deftype_method_param_bindings( + ctx, params, special_form + ) if len(param_nodes) > 0: raise AnalyzerException( - "deftype* properties may not specify arguments", form=form + f"{special_form} properties may not specify arguments", form=form ) - assert not has_vargs, "deftype* properties may not have arguments" + assert not has_vargs, f"{special_form} properties may not have arguments" with ctx.new_func_ctx(FunctionContext.PROPERTY), ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) @@ -1215,11 +1229,13 @@ def __deftype_staticmethod( args: vec.Vector, kwarg_support: Optional[KeywordArgSupport] = None, ) -> DefTypeStaticMethodArity: - """Emit a node for a :staticmethod member of a deftype* form.""" + """Emit a node for a :staticmethod member of a `deftype*` form.""" with ctx.hide_parent_symbol_table(), ctx.new_symbol_table( method_name, is_context_boundary=True ): - has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings(ctx, args) + has_vargs, fixed_arity, param_nodes = __deftype_method_param_bindings( + ctx, args, SpecialForm.DEFTYPE + ) with ctx.new_func_ctx(FunctionContext.STATICMETHOD), ctx.expr_pos(): stmts, ret = _body_ast(ctx, runtime.nthrest(form, 2)) method = DefTypeStaticMethodArity( @@ -1244,10 +1260,11 @@ def __deftype_staticmethod( return method -def __deftype_prop_or_method_arity( # pylint: disable=too-many-branches - ctx: AnalyzerContext, form: Union[llist.List, ISeq] +def __deftype_or_reify_prop_or_method_arity( # pylint: disable=too-many-branches + ctx: AnalyzerContext, form: Union[llist.List, ISeq], special_form: sym.Symbol ) -> Union[DefTypeMethodArityBase, DefTypeProperty]: - """Emit either a `deftype*` property node or an arity of a `deftype*` method. + """Emit either a `deftype*` or `reify*` property node or an arity of a `deftype*` + or `reify*` method. Unlike standard `fn*` definitions, multiple arities for a single method are not defined within some containing node. As such, we can only emit either a @@ -1257,9 +1274,11 @@ def __deftype_prop_or_method_arity( # pylint: disable=too-many-branches The type of the member node is determined by the presence or absence of certain metadata elements on the input form (or the form's first member, typically a symbol naming that member).""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + if not isinstance(form.first, sym.Symbol): raise AnalyzerException( - "deftype* method must be named by symbol: (name [& args] & body)", + f"{special_form} method must be named by symbol: (name [& args] & body)", form=form.first, ) method_name = form.first.name @@ -1274,16 +1293,24 @@ def __deftype_prop_or_method_arity( # pylint: disable=too-many-branches isinstance(form, IMeta) and _is_py_staticmethod(form) ) + if special_form == SpecialForm.REIFY and (is_classmethod or is_staticmethod): + raise AnalyzerException( + f"{special_form} does not support classmethod or staticmethod members", + form=form, + ) + if not sum([is_classmethod, is_property, is_staticmethod]) in {0, 1}: raise AnalyzerException( - "deftype* member may be only one of: :classmethod, :property, or :staticmethod", + f"{special_form} member may be only one of: :classmethod, :property, " + "or :staticmethod", form=form, ) args = runtime.nth(form, 1) if not isinstance(args, vec.Vector): raise AnalyzerException( - f"deftype* member arguments must be vector, not {type(args)}", form=args + f"{special_form} member arguments must be vector, not {type(args)}", + form=args, ) kwarg_meta = __fn_kwargs_support(form.first) or ( @@ -1298,27 +1325,31 @@ def __deftype_prop_or_method_arity( # pylint: disable=too-many-branches elif is_property: if kwarg_support is not None: raise AnalyzerException( - f"deftype* properties may not declare keyword argument support", + f"{special_form} properties may not declare keyword argument support", form=form, ) - return __deftype_property(ctx, form, method_name, args) + return __deftype_or_reify_property(ctx, form, method_name, args, special_form) elif is_staticmethod: return __deftype_staticmethod( ctx, form, method_name, args, kwarg_support=kwarg_support ) else: - return __deftype_method( - ctx, form, method_name, args, kwarg_support=kwarg_support + return __deftype_or_reify_method( + ctx, form, method_name, args, special_form, kwarg_support=kwarg_support ) -def __deftype_method_node_from_arities( # pylint: disable=too-many-branches +def __deftype_or_reify_method_node_from_arities( # pylint: disable=too-many-branches ctx: AnalyzerContext, form: Union[llist.List, ISeq], arities: List[DefTypeMethodArityBase], + special_form: sym.Symbol, ) -> DefTypeMethodBase: - """Roll all of the collected arities up into a single method node.""" + """Roll all of the collected `deftype*` or `reify*` arities up into a single + method node.""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + fixed_arities: MutableSet[int] = set() fixed_arity_for_variadic: Optional[int] = None num_variadic = 0 @@ -1326,21 +1357,23 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches if fixed_arity_for_variadic is not None: if arity.fixed_arity >= fixed_arity_for_variadic: raise AnalyzerException( - "deftype method may not have a method with fixed arity greater " - "than fixed arity of variadic function", + f"{special_form} method may not have a method with fixed arity " + "greater than fixed arity of variadic function", form=arity.form, ) if arity.is_variadic: if num_variadic > 0: raise AnalyzerException( - "deftype method may have at most 1 variadic arity", form=arity.form + f"{special_form} method may have at most 1 variadic arity", + form=arity.form, ) fixed_arity_for_variadic = arity.fixed_arity num_variadic += 1 else: if arity.fixed_arity in fixed_arities: raise AnalyzerException( - "deftype may not have multiple methods with the same fixed arity", + f"{special_form} may not have multiple methods with the same " + "fixed arity", form=arity.form, ) fixed_arities.add(arity.fixed_arity) @@ -1359,7 +1392,8 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches if len(arities) > 1 and any(arity.kwarg_support is not None for arity in arities): raise AnalyzerException( - "multi-arity deftype* methods may not declare support for keyword arguments", + f"multi-arity {special_form} methods may not declare support for " + "keyword arguments", form=form, ) @@ -1400,22 +1434,26 @@ def __deftype_method_node_from_arities( # pylint: disable=too-many-branches ) -def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noqa: MC0001 - ctx: AnalyzerContext, form: ISeq +def __deftype_or_reify_impls( # pylint: disable=too-many-branches,too-many-locals # noqa: MC0001 + ctx: AnalyzerContext, form: ISeq, special_form: sym.Symbol, ) -> Tuple[List[DefTypeBase], List[DefTypeMember]]: - """Roll up deftype* declared bases and method implementations.""" + """Roll up `deftype*` and `reify*` declared bases and method implementations.""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + if runtime.to_seq(form) is None: return [], [] if not isinstance(form.first, kw.Keyword) or form.first != IMPLEMENTS: raise AnalyzerException( - "deftype* forms must declare which interfaces they implement", form=form + f"{special_form} forms must declare which interfaces they implement", + form=form, ) implements = runtime.nth(form, 1) if not isinstance(implements, vec.Vector): raise AnalyzerException( - "deftype* interfaces must be declared as :implements [Interface1 Interface2 ...]", + f"{special_form} interfaces must be declared as " + ":implements [Interface1 Interface2 ...]", form=implements, ) @@ -1423,11 +1461,13 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq interfaces = [] for iface in implements: if not isinstance(iface, sym.Symbol): - raise AnalyzerException("deftype* interfaces must be symbols", form=iface) + raise AnalyzerException( + f"{special_form} interfaces must be symbols", form=iface + ) if iface in interface_names: raise AnalyzerException( - "deftype* interfaces may only appear once in :implements vector", + f"{special_form} interfaces may only appear once in :implements vector", form=iface, ) interface_names.add(iface) @@ -1435,7 +1475,7 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq current_interface = _analyze_form(ctx, iface) if not isinstance(current_interface, (MaybeClass, MaybeHostForm, VarRef)): raise AnalyzerException( - "deftype* interface implementation must be an existing interface", + f"{special_form} interface implementation must be an existing interface", form=iface, ) interfaces.append(current_interface) @@ -1451,22 +1491,22 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq for elem in runtime.nthrest(form, 2): if not isinstance(elem, ISeq): raise AnalyzerException( - "deftype* must consist of interface or protocol names and methods", + f"{special_form} must consist of interface or protocol names and methods", form=elem, ) - member = __deftype_prop_or_method_arity(ctx, elem) + member = __deftype_or_reify_prop_or_method_arity(ctx, elem, special_form) member_order[member.name] = True if isinstance(member, DefTypeProperty): if member.name in props: raise AnalyzerException( - "deftype* property may only have one arity defined", + f"{special_form} property may only have one arity defined", form=elem, lisp_ast=member, ) elif member.name in methods: raise AnalyzerException( - "deftype* property name already defined as a method", + f"{special_form} property name already defined as a method", form=elem, lisp_ast=member, ) @@ -1474,7 +1514,7 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq else: if member.name in props: raise AnalyzerException( - "deftype* method name already defined as a property", + f"{special_form} method name already defined as a property", form=elem, lisp_ast=member, ) @@ -1484,7 +1524,11 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq for member_name in member_order: arities = methods.get(member_name) if arities is not None: - members.append(__deftype_method_node_from_arities(ctx, form, arities)) + members.append( + __deftype_or_reify_method_node_from_arities( + ctx, form, arities, special_form + ) + ) continue prop = props.get(member_name) @@ -1494,21 +1538,24 @@ def __deftype_impls( # pylint: disable=too-many-branches,too-many-locals # noq return interfaces, members -def __deftype_impls_are_all_abstract( # pylint: disable=too-many-branches,too-many-locals +def __deftype_and_reify_impls_are_all_abstract( # pylint: disable=too-many-branches,too-many-locals + special_form: sym.Symbol, fields: Iterable[str], interfaces: Iterable[DefTypeBase], members: Iterable[DefTypeMember], ) -> bool: - """Return True if all `deftype*` super-types can be verified abstract statically. - Return False otherwise. + """Return True if all `deftype*` or `reify*` super-types can be verified abstract + statically. Return False otherwise. - In certain cases, such as in macro definitions and potentially inside of functions, - the compiler will be unable to resolve the named super-type as an object during - compilation and these checks will need to be deferred to runtime. In these cases, - the compiler will wrap the emitted class in a decorator that performs the checks - when the class is compiled by the Python compiler. + In certain cases, such as in macro definitions and potentially inside of + functions, the compiler will be unable to resolve the named super-type as an + object during compilation and these checks will need to be deferred to runtime. + In these cases, the compiler will wrap the emitted class in a decorator that + performs the checks when the class is compiled by the Python compiler. For normal compile-time errors, an `AnalyzerException` will be raised.""" + assert special_form in {SpecialForm.DEFTYPE, SpecialForm.REIFY} + field_names = frozenset(fields) member_names = frozenset(munge(member.name) for member in members) all_member_names = field_names.union(member_names) @@ -1523,8 +1570,8 @@ def __deftype_impls_are_all_abstract( # pylint: disable=too-many-branches,too-m if not interface.var.is_bound: logger.log( TRACE, - f"deftype* interface Var '{interface.form}' is not bound and " - "cannot be checked for abstractness; deferring to runtime", + f"{special_form} interface Var '{interface.form}' is not bound" + "and cannot be checked for abstractness; deferring to runtime", ) return False interface_type = interface.var.value @@ -1534,7 +1581,7 @@ def __deftype_impls_are_all_abstract( # pylint: disable=too-many-branches,too-m if not is_abstract(interface_type): raise AnalyzerException( - "deftype* interface must be Python abstract class or object", + f"{special_form} interface must be Python abstract class or object", form=interface.form, lisp_ast=interface, ) @@ -1549,16 +1596,16 @@ def __deftype_impls_are_all_abstract( # pylint: disable=too-many-branches,too-m if not interface_method_names.issubset(member_names): missing_methods = ", ".join(interface_method_names - member_names) raise AnalyzerException( - "deftype* definition missing interface members for interface " - f"{interface.form}: {missing_methods}", + f"{special_form} definition missing interface members for " + f"interface {interface.form}: {missing_methods}", form=interface.form, lisp_ast=interface, ) elif not interface_property_names.issubset(all_member_names): missing_fields = ", ".join(interface_property_names - field_names) raise AnalyzerException( - "deftype* definition missing interface properties for interface " - f"{interface.form}: {missing_fields}", + f"{special_form} definition missing interface properties for " + f"interface {interface.form}: {missing_fields}", form=interface.form, lisp_ast=interface, ) @@ -1569,8 +1616,8 @@ def __deftype_impls_are_all_abstract( # pylint: disable=too-many-branches,too-m if extra_methods: extra_method_str = ", ".join(extra_methods) raise AnalyzerException( - "deftype* definition for interface includes members not part of " - f"defined interfaces: {extra_method_str}" + f"{special_form} definition for interface includes members not " + f"part of defined interfaces: {extra_method_str}" ) return True @@ -1652,9 +1699,11 @@ def _deftype_ast( # pylint: disable=too-many-branches param_nodes.append(binding) ctx.put_new_symbol(field, binding, warn_if_unused=False) - interfaces, members = __deftype_impls(ctx, runtime.nthrest(form, 3)) - verified_abstract = __deftype_impls_are_all_abstract( - map(lambda f: f.name, fields), interfaces, members + interfaces, members = __deftype_or_reify_impls( + ctx, runtime.nthrest(form, 3), SpecialForm.DEFTYPE + ) + verified_abstract = __deftype_and_reify_impls_are_all_abstract( + SpecialForm.DEFTYPE, map(lambda f: f.name, fields), interfaces, members ) return DefType( form=form, @@ -2478,6 +2527,10 @@ def _assert_recur_is_tail(node: Node) -> None: # pylint: disable=too-many-branc _assert_no_recur(binding.init) elif node.op == NodeOp.RECUR: pass + elif node.op == NodeOp.REIFY: + assert isinstance(node, Reify) + for child in node.members: + _assert_recur_is_tail(child) elif node.op == NodeOp.TRY: assert isinstance(node, Try) _assert_recur_is_tail(node.body) @@ -2508,6 +2561,34 @@ def _recur_ast(ctx: AnalyzerContext, form: ISeq) -> Recur: ) +@_with_meta +def _reify_ast(ctx: AnalyzerContext, form: ISeq) -> Reify: + assert form.first == SpecialForm.REIFY + + nelems = count(form) + if nelems < 3: + raise AnalyzerException( + "reify forms must have 3 or more elements, as in: " + "(reify* :implements [bases+impls])", + form=form, + ) + + with ctx.new_symbol_table("reify"): + interfaces, members = __deftype_or_reify_impls( + ctx, runtime.nthrest(form, 1), SpecialForm.REIFY + ) + verified_abstract = __deftype_and_reify_impls_are_all_abstract( + SpecialForm.REIFY, (), interfaces, members + ) + return Reify( + form=form, + interfaces=vec.vector(interfaces), + members=vec.vector(members), + verified_abstract=verified_abstract, + env=ctx.get_node_env(pos=ctx.syntax_position), + ) + + def _require_ast( # pylint: disable=too-many-branches ctx: AnalyzerContext, form: ISeq ) -> Require: @@ -2758,6 +2839,7 @@ def _var_ast(ctx: AnalyzerContext, form: ISeq) -> VarRef: SpecialForm.LOOP: _loop_ast, SpecialForm.QUOTE: _quote_ast, SpecialForm.RECUR: _recur_ast, + SpecialForm.REIFY: _reify_ast, SpecialForm.REQUIRE: _require_ast, SpecialForm.SET_BANG: _set_bang_ast, SpecialForm.THROW: _throw_ast, @@ -3219,7 +3301,7 @@ def _const_node(ctx: AnalyzerContext, form: ReaderForm) -> Const: uuid.UUID, ), ) - ) + ), "Constant nodes must be composed of constant values" node_type = _CONST_NODE_TYPES.get(type(form), ConstType.UNKNOWN) if node_type == ConstType.UNKNOWN: diff --git a/src/basilisp/lang/compiler/constants.py b/src/basilisp/lang/compiler/constants.py index 6b765aa76..dd9755544 100644 --- a/src/basilisp/lang/compiler/constants.py +++ b/src/basilisp/lang/compiler/constants.py @@ -19,6 +19,7 @@ class SpecialForm: LOOP = sym.symbol("loop*") QUOTE = sym.symbol("quote") RECUR = sym.symbol("recur") + REIFY = sym.symbol("reify*") REQUIRE = sym.symbol("require*") SET_BANG = sym.symbol("set!") THROW = sym.symbol("throw") diff --git a/src/basilisp/lang/compiler/generator.py b/src/basilisp/lang/compiler/generator.py index e8c4fcf31..a1105a61d 100644 --- a/src/basilisp/lang/compiler/generator.py +++ b/src/basilisp/lang/compiler/generator.py @@ -90,6 +90,7 @@ Quote, ReaderLispForm, Recur, + Reify, Require, Set as SetNode, SetBang, @@ -375,6 +376,85 @@ def _collection_ast( return _chain_py_ast(*map(partial(gen_py_ast, ctx), form)) +# `attrs` throws an a ValueError for certain small class definitions, so +# we want to avoid that. +# +# https://github.com/python-attrs/attrs/issues/589 +_ATTR_SLOTS_ON = getattr(attr, "__version_info__", (0,)) >= (19, 4) + +_ATTR_CMP_OFF = getattr(attr, "__version_info__", (0,)) >= (19, 2) +_ATTR_CMP_KWARGS = ( + [ + ast.keyword(arg="eq", value=ast.Constant(False)), + ast.keyword(arg="order", value=ast.Constant(False)), + ] + if _ATTR_CMP_OFF + else [ast.keyword(arg="cmp", value=ast.Constant(False))] +) + + +def _class_ast( # pylint: disable=too-many-arguments + class_name: str, + body: List[ast.AST], + bases: Iterable[ast.AST] = (), + fields: Iterable[str] = (), + members: Iterable[str] = (), + verified_abstract: bool = False, + is_frozen: bool = True, + use_slots: bool = _ATTR_SLOTS_ON, +) -> ast.ClassDef: + """Return a Python class definition for `deftype` and `reify` special forms.""" + return ast.ClassDef( + name=class_name, + bases=bases, + keywords=[], + body=body, + decorator_list=list( + chain( + [] + if verified_abstract + else [ + ast.Call( + func=_BASILISP_TYPE_FN_NAME, + args=[], + keywords=[ + ast.keyword( + arg="fields", + value=ast.Tuple( + elts=[ast.Constant(e) for e in fields], + ctx=ast.Load(), + ), + ), + ast.keyword( + arg="interfaces", + value=ast.Tuple(elts=list(bases), ctx=ast.Load()), + ), + ast.keyword( + arg="members", + value=ast.Tuple( + elts=[ast.Constant(e) for e in members], + ctx=ast.Load(), + ), + ), + ], + ) + ], + [ + ast.Call( + func=_ATTR_CLASS_DECORATOR_NAME, + args=[], + keywords=_ATTR_CMP_KWARGS + + [ + ast.keyword(arg="frozen", value=ast.Constant(is_frozen)), + ast.keyword(arg="slots", value=ast.Constant(use_slots)), + ], + ), + ], + ) + ), + ) + + def _kwargs_ast( ctx: GeneratorContext, kwargs: KeywordArgs, ) -> Tuple[PyASTStream, PyASTStream]: @@ -565,6 +645,7 @@ def _var_ns_as_python_sym(name: str) -> str: _BASILISP_FN_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_fn") _FN_WITH_ATTRS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._with_attrs") _BASILISP_TYPE_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._basilisp_type") +_BASILISP_WITH_META_INTERFACE_NAME = _load_attr(f"{_INTERFACES_ALIAS}.IWithMeta") _BUILTINS_IMPORT_FN_NAME = _load_attr("builtins.__import__") _IMPORTLIB_IMPORT_MODULE_FN_NAME = _load_attr("importlib.import_module") _LISP_FN_APPLY_KWARGS_FN_NAME = _load_attr(f"{_RUNTIME_ALIAS}._lisp_fn_apply_kwargs") @@ -821,7 +902,7 @@ def __single_arity_deftype_method_to_py_ast( prefix_args: Iterable[ast.arg] = (), decorators: Iterable[ast.AST] = (), ) -> GeneratedPyAST: - """Generate a single arity deftype* method body.""" + """Generate a single arity `deftype*` or `reify*` method body.""" fn_args, varg, fn_body_ast = __fn_args_to_py_ast(ctx, arity.params, arity.body) return GeneratedPyAST( node=ast.FunctionDef( @@ -851,7 +932,7 @@ def __multi_arity_deftype_dispatch_method( # pylint: disable=too-many-arguments decorators: Iterable[ast.AST] = (), ) -> GeneratedPyAST: """Return the Python AST nodes for an argument-length dispatch method for - multi-arity deftype* methods. + multi-arity `deftype*` or `reify*` methods. The `arity_map` names the mapping of number of arguments to the munged name of the method arity handling that method. `default_name` is the name of the default @@ -1056,7 +1137,7 @@ def __multi_arity_deftype_method_to_py_ast( # pylint: disable=too-many-argument fn_def = create_method_ast(ctx, node, arity, arity_name) assert ( not fn_def.dependencies - ), "deftype* method arities may not have dependency nodes" + ), "deftype* or reify* method arities may not have dependency nodes" fn_defs.append(fn_def.node) dispatch_fn_ast = __multi_arity_deftype_dispatch_method( @@ -1102,9 +1183,9 @@ def __deftype_classmethod_arity_to_py_ast( @_with_ast_loc def __deftype_classmethod_to_py_ast( - ctx: GeneratorContext, node: DefTypeClassMethod, _: DefType, + ctx: GeneratorContext, node: DefTypeClassMethod, _: str, ) -> GeneratedPyAST: - """Return a Python AST Node for a `deftype*` classmethod.""" + """Return a Python AST Node for a `deftype*` or `reify*` classmethod.""" assert node.op == NodeOp.DEFTYPE_CLASSMETHOD if len(node.arities) == 1: @@ -1123,7 +1204,7 @@ def __deftype_classmethod_to_py_ast( @_with_ast_loc def __deftype_property_to_py_ast( - ctx: GeneratorContext, node: DefTypeProperty, _: DefType, + ctx: GeneratorContext, node: DefTypeProperty, _: str, ) -> GeneratedPyAST: assert node.op == NodeOp.DEFTYPE_PROPERTY method_name = munge(node.name) @@ -1194,9 +1275,9 @@ def _should_trampoline() -> Iterable[ast.AST]: @_with_ast_loc def __deftype_method_to_py_ast( - ctx: GeneratorContext, node: DefTypeMethod, _: DefType, + ctx: GeneratorContext, node: DefTypeMethod, _: str, ) -> GeneratedPyAST: - """Return a Python AST Node for a `deftype*` method.""" + """Return a Python AST Node for a `deftype*` or `reify*` method.""" assert node.op == NodeOp.DEFTYPE_METHOD if len(node.arities) == 1: @@ -1232,9 +1313,9 @@ def __deftype_staticmethod_arity_to_py_ast( @_with_ast_loc def __deftype_staticmethod_to_py_ast( - ctx: GeneratorContext, node: DefTypeStaticMethod, parent: DefType + ctx: GeneratorContext, node: DefTypeStaticMethod, class_name: str ) -> GeneratedPyAST: - """Return a Python AST Node for a `deftype*` staticmethod.""" + """Return a Python AST Node for a `deftype*` or `reify*` staticmethod.""" assert node.op == NodeOp.DEFTYPE_STATICMETHOD if len(node.arities) == 1: @@ -1246,14 +1327,12 @@ def __deftype_staticmethod_to_py_ast( ctx, node, cast(_CreateMethodASTFunction, __deftype_staticmethod_arity_to_py_ast), - class_name=munge(parent.name), + class_name=class_name, decorators=(_PY_STATICMETHOD_FN_NAME,), ) -DefTypeASTGenerator = Callable[ - [GeneratorContext, DefTypeMember, DefType], GeneratedPyAST -] +DefTypeASTGenerator = Callable[[GeneratorContext, DefTypeMember, str], GeneratedPyAST] _DEFTYPE_MEMBER_HANDLER: Mapping[NodeOp, DefTypeASTGenerator] = { NodeOp.DEFTYPE_CLASSMETHOD: __deftype_classmethod_to_py_ast, NodeOp.DEFTYPE_METHOD: __deftype_method_to_py_ast, @@ -1263,25 +1342,14 @@ def __deftype_staticmethod_to_py_ast( def __deftype_member_to_py_ast( - ctx: GeneratorContext, node: DefTypeMember, parent: DefType, + ctx: GeneratorContext, node: DefTypeMember, class_name: str, ) -> GeneratedPyAST: member_type = node.op handle_deftype_member = _DEFTYPE_MEMBER_HANDLER.get(member_type) assert ( handle_deftype_member is not None ), f"Invalid :const AST type handler for {member_type}" - return handle_deftype_member(ctx, node, parent) - - -_ATTR_CMP_OFF = getattr(attr, "__version_info__", (0,)) >= (19, 2) -_ATTR_CMP_KWARGS = ( - [ - ast.keyword(arg="eq", value=ast.Constant(False)), - ast.keyword(arg="order", value=ast.Constant(False)), - ] - if _ATTR_CMP_OFF - else [ast.keyword(arg="cmp", value=ast.Constant(False))] -) + return handle_deftype_member(ctx, node, class_name) @_with_ast_loc @@ -1329,7 +1397,7 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches,too-many-locals fields.append(safe_field) for member in node.members: - type_ast = __deftype_member_to_py_ast(ctx, member, node) + type_ast = __deftype_member_to_py_ast(ctx, member, munge(node.name)) type_nodes.append(type_ast.node) type_nodes.extend(type_ast.dependencies) members.append(munge(member.name)) @@ -1340,69 +1408,15 @@ def _deftype_to_py_ast( # pylint: disable=too-many-branches,too-many-locals chain( type_deps, [ - ast.ClassDef( - name=type_name, + _class_ast( + type_name, + type_nodes or [ast.Pass()], bases=bases, - keywords=[], - body=type_nodes or [ast.Pass()], - decorator_list=list( - chain( - [] - if node.verified_abstract - else [ - ast.Call( - func=_BASILISP_TYPE_FN_NAME, - args=[], - keywords=[ - ast.keyword( - arg="fields", - value=ast.Tuple( - elts=[ - ast.Constant(e) - for e in fields - ], - ctx=ast.Load(), - ), - ), - ast.keyword( - arg="interfaces", - value=ast.Tuple( - elts=list(bases), - ctx=ast.Load(), - ), - ), - ast.keyword( - arg="members", - value=ast.Tuple( - elts=[ - ast.Constant(e) - for e in members - ], - ctx=ast.Load(), - ), - ), - ], - ) - ], - [ - ast.Call( - func=_ATTR_CLASS_DECORATOR_NAME, - args=[], - keywords=_ATTR_CMP_KWARGS - + [ - ast.keyword( - arg="frozen", - value=ast.Constant(node.is_frozen), - ), - ast.keyword( - arg="slots", - value=ast.Constant(True), - ), - ], - ), - ], - ) - ), + fields=fields, + members=members, + verified_abstract=node.verified_abstract, + is_frozen=node.is_frozen, + use_slots=True, ), ast.Call( func=_INTERN_VAR_FN_NAME, @@ -2353,6 +2367,114 @@ def _recur_to_py_ast(ctx: GeneratorContext, node: Recur) -> GeneratedPyAST: return handle_recur(ctx, node) +@_with_ast_loc +def _reify_to_py_ast( + ctx: GeneratorContext, node: Reify, meta_node: Optional[MetaNode] = None +) -> GeneratedPyAST: + """Return a Python AST Node for a `reify*` expression.""" + assert node.op == NodeOp.REIFY + + meta_ast: Optional[GeneratedPyAST] + if meta_node is not None: + meta_ast = gen_py_ast(ctx, meta_node) + else: + meta_ast = None + + bases: List[ast.AST] = [_BASILISP_WITH_META_INTERFACE_NAME] + for base in node.interfaces: + base_node = gen_py_ast(ctx, base) + assert ( + count(base_node.dependencies) == 0 + ), "Class and host form nodes do not have dependencies" + bases.append(base_node.node) + + type_name = munge(genname("ReifiedType")) + + with ctx.new_symbol_table("reify"): + members = ["meta", "with_meta"] + type_nodes: List[ast.AST] = [ + ast.Assign( + targets=[ast.Name(id="_meta", ctx=ast.Store())], + value=ast.Call( + func=_ATTRIB_FIELD_FN_NAME, + args=[], + keywords=[ast.keyword(arg="default", value=ast.Constant(None))], + ), + ), + ast.FunctionDef( + name="meta", + args=ast.arguments( + args=[ast.arg(arg="self", annotation=None),], + kwarg=None, + vararg=None, + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=[ast.Return(value=_load_attr("self._meta"))], + decorator_list=[], + returns=None, + ), + ast.FunctionDef( + name="with_meta", + args=ast.arguments( + args=[ + ast.arg(arg="self", annotation=None), + ast.arg(arg="new_meta", annotation=None), + ], + kwarg=None, + vararg=None, + kwonlyargs=[], + defaults=[], + kw_defaults=[], + ), + body=[ + ast.Return( + value=ast.Call( + func=ast.Name(id=type_name, ctx=ast.Load()), + args=[ast.Name(id="new_meta", ctx=ast.Load())], + keywords=[], + ) + ) + ], + decorator_list=[], + returns=None, + ), + ] + type_deps: List[ast.AST] = [] + + for member in node.members: + type_ast = __deftype_member_to_py_ast(ctx, member, type_name) + type_nodes.append(type_ast.node) + type_nodes.extend(type_ast.dependencies) + members.append(munge(member.name)) + + return GeneratedPyAST( + node=ast.Call( + func=ast.Name(id=type_name, ctx=ast.Load()), + args=[] if meta_ast is None else [meta_ast.node], + keywords=[], + ), + dependencies=list( + chain( + type_deps, + [] if meta_ast is None else meta_ast.dependencies, + [ + _class_ast( + type_name, + type_nodes, + bases=bases, + members=members, + verified_abstract=node.verified_abstract, + is_frozen=True, + use_slots=_ATTR_SLOTS_ON, + ) + ], + ) + ), + ) + + @_with_ast_loc_deps def _require_to_py_ast(_: GeneratorContext, node: Require) -> GeneratedPyAST: """Return a Python AST node for a Basilisp `require*` expression. @@ -2815,8 +2937,9 @@ def _map_to_py_ast( ) -> GeneratedPyAST: assert node.op == NodeOp.MAP + meta_ast: Optional[GeneratedPyAST] if meta_node is not None: - meta_ast: Optional[GeneratedPyAST] = gen_py_ast(ctx, meta_node) + meta_ast = gen_py_ast(ctx, meta_node) else: meta_ast = None @@ -2846,8 +2969,9 @@ def _set_to_py_ast( ) -> GeneratedPyAST: assert node.op == NodeOp.SET + meta_ast: Optional[GeneratedPyAST] if meta_node is not None: - meta_ast: Optional[GeneratedPyAST] = gen_py_ast(ctx, meta_node) + meta_ast = gen_py_ast(ctx, meta_node) else: meta_ast = None @@ -2874,8 +2998,9 @@ def _vec_to_py_ast( ) -> GeneratedPyAST: assert node.op == NodeOp.VECTOR + meta_ast: Optional[GeneratedPyAST] if meta_node is not None: - meta_ast: Optional[GeneratedPyAST] = gen_py_ast(ctx, meta_node) + meta_ast = gen_py_ast(ctx, meta_node) else: meta_ast = None @@ -2950,6 +3075,7 @@ def _py_tuple_to_py_ast(ctx: GeneratorContext, node: PyTuple) -> GeneratedPyAST: _WITH_META_EXPR_HANDLER = { NodeOp.FN: _fn_to_py_ast, NodeOp.MAP: _map_to_py_ast, + NodeOp.REIFY: _reify_to_py_ast, NodeOp.SET: _set_to_py_ast, NodeOp.VECTOR: _vec_to_py_ast, } @@ -3354,6 +3480,7 @@ def _const_node_to_py_ast(ctx: GeneratorContext, lisp_ast: Const) -> GeneratedPy NodeOp.PY_TUPLE: _py_tuple_to_py_ast, NodeOp.QUOTE: _quote_to_py_ast, NodeOp.RECUR: _recur_to_py_ast, # type: ignore + NodeOp.REIFY: _reify_to_py_ast, NodeOp.REQUIRE: _require_to_py_ast, NodeOp.SET: _set_to_py_ast, NodeOp.SET_BANG: _set_bang_to_py_ast, diff --git a/src/basilisp/lang/compiler/nodes.py b/src/basilisp/lang/compiler/nodes.py index 9652f18d9..2877ba156 100644 --- a/src/basilisp/lang/compiler/nodes.py +++ b/src/basilisp/lang/compiler/nodes.py @@ -92,6 +92,7 @@ class NodeOp(Enum): PY_TUPLE = kw.keyword("py-tuple") QUOTE = kw.keyword("quote") RECUR = kw.keyword("recur") + REIFY = kw.keyword("reify") REQUIRE = kw.keyword("require") REQUIRE_ALIAS = kw.keyword("require-alias") SET = kw.keyword("set") @@ -810,6 +811,20 @@ class Recur(Node[SpecialForm]): raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() +@attr.s(auto_attribs=True, frozen=True, slots=True) +class Reify(Node[SpecialForm]): + form: SpecialForm + interfaces: Iterable[DefTypeBase] + members: Iterable["DefTypeMember"] + env: NodeEnv + verified_abstract: bool = False + meta: NodeMeta = None + children: Sequence[kw.Keyword] = vec.v(MEMBERS) + op: NodeOp = NodeOp.REIFY + top_level: bool = False + raw_forms: IPersistentVector[LispForm] = vec.Vector.empty() + + @attr.s(auto_attribs=True, frozen=True, slots=True) class RequireAlias(Node[Union[sym.Symbol, vec.Vector]]): form: Union[sym.Symbol, vec.Vector] @@ -935,6 +950,7 @@ class WithMeta(Node[LispForm]): Loop, Quote, Recur, + Reify, Require, SetBang, Throw, diff --git a/src/basilisp/lang/runtime.py b/src/basilisp/lang/runtime.py index fabc513a2..c22c87968 100644 --- a/src/basilisp/lang/runtime.py +++ b/src/basilisp/lang/runtime.py @@ -96,6 +96,7 @@ _LETFN = sym.symbol("letfn*") _LOOP = sym.symbol("loop*") _QUOTE = sym.symbol("quote") +_REIFY = sym.symbol("reify*") _RECUR = sym.symbol("recur") _REQUIRE = sym.symbol("require*") _SET_BANG = sym.symbol("set!") @@ -119,6 +120,7 @@ _LOOP, _QUOTE, _RECUR, + _REIFY, _REQUIRE, _SET_BANG, _THROW, diff --git a/tests/basilisp/compiler_test.py b/tests/basilisp/compiler_test.py index 5c558c632..378680f85 100644 --- a/tests/basilisp/compiler_test.py +++ b/tests/basilisp/compiler_test.py @@ -25,7 +25,7 @@ import basilisp.lang.symbol as sym import basilisp.lang.vector as vec from basilisp.lang.compiler.constants import SYM_PRIVATE_META_KEY -from basilisp.lang.interfaces import IType +from basilisp.lang.interfaces import IType, IWithMeta from basilisp.lang.runtime import Var from basilisp.lang.util import demunge from tests.basilisp.helpers import get_or_create_ns @@ -3876,6 +3876,873 @@ def test_multi_arity_named_anonymous_fn_recursion(self, lcompile: CompileFn): assert 15 == lcompile(code) +class TestReify: + @pytest.mark.parametrize("code", ["(reify*)", "(reify* :implements)"]) + def test_reify_number_of_elems(self, lcompile: CompileFn, code: str): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_has_implements_kw(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (reify* [collections.abc/Sized] + (__len__ [this] 2))""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (reify :implements (collections.abc/Sized) + (__len__ [this] 2))""", + """ + (reify :implements collections.abc/Sized + (__len__ [this] 2))""", + ], + ) + def test_reify_implements_is_vector(self, lcompile: CompileFn, code: str): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_must_declare_implements(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (reify* + (--call-- [this] [1 2 3]))""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (reify :implements [collections.abc/Callable])""", + """ + (import* collections.abc) + (reify* :implements [collections.abc/Callable collections.abc/Sized] + (--call-- [this] [1 2 3]))""", + """ + (reify* + (--call-- [this] [1 2 3]))""", + ], + ) + def test_reify_impls_must_match_defined_interfaces( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (reify* :implements [collections.abc/Callable collections.abc/Callable] + (--call-- [this] [1 2 3]) + (--call-- [this] 1))""", + """ + (import* collections.abc) + (reify* + :implements [collections.abc/Callable collections.abc/Sized collections.abc/Callable] + (--call-- [this] [1 2 3]) + (--len-- [this] 1) + (--call-- [this] 1))""", + """ + (import* collections.abc) + (reify* + :implements [collections.abc/Callable collections.abc/Callable collections.abc/Sized] + (--call-- [this] [1 2 3]) + (--call-- [this] 1) + (--len-- [this] 1))""", + ], + ) + def test_reify_prohibit_duplicate_interface(self, lcompile: CompileFn, code: str): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (reify* + :implements [:collections.abc/Callable] + (--call-- [this] [1 2 3]))""", + """ + (import* collections.abc) + (reify* + :implements [collections.abc/Callable] + [--call-- [this] [1 2 3]])""", + """ + (import* collections.abc) + (reify* + :implements [collections.abc/Callable :collections.abc/Sized] + [--call-- [this] [1 2 3]])""", + ], + ) + def test_reify_impls_must_be_sym_or_list(self, lcompile: CompileFn, code: str): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_interface_must_be_host_form(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (let [a :kw] + (reify* :implements [a] + (--call-- [this] [1 2 3])))""" + ) + + @pytest.mark.parametrize( + "code,ExceptionType", + [ + ( + """ + (import* collections) + (reify* :implements [collections/OrderedDict] + (keys [this] [1 2 3]))""", + compiler.CompilerException, + ), + ( + """ + (do + (def Shape (python/type "Shape" #py () #py {})) + (reify* :implements [Shape]))""", + runtime.RuntimeException, + ), + ], + ) + def test_reify_interface_must_be_abstract( + self, lcompile: CompileFn, code: str, ExceptionType + ): + with pytest.raises(ExceptionType): + lcompile(code) + + def test_reify_allows_empty_abstract_interface(self, lcompile: CompileFn): + reified_obj = lcompile("(reify* :implements [basilisp.lang.interfaces/IType])") + assert isinstance(reified_obj, IType) + + def test_reify_allows_empty_dynamic_abstract_interface(self, lcompile: CompileFn): + Shape, make_circle = lcompile( + """ + (do + (import* abc) + (def Shape (python/type "Shape" #py (abc/ABC) #py {})) + [Shape (fn [] (reify* :implements [Shape]))])""" + ) + c = make_circle() + assert isinstance(Shape, type) + assert isinstance(c, Shape) + + @pytest.mark.parametrize( + "code,ExceptionType", + [ + ( + """ + (import* collections.abc) + (reify* :implements [collections.abc/Collection] + (--len-- [this] 3))""", + compiler.CompilerException, + ), + ( + """ + (do + (import* abc) + (def Shape + (python/type "Shape" + #py (abc/ABC) + #py {"area" + (abc/abstractmethod + (fn []))})) + (reify* :implements [Shape]))""", + runtime.RuntimeException, + ), + ], + ) + def test_reify_interface_must_implement_all_abstract_methods( + self, lcompile: CompileFn, code: str, ExceptionType, + ): + with pytest.raises(ExceptionType): + lcompile(code) + + @pytest.mark.parametrize( + "code,ExceptionType", + [ + ( + """ + (import* collections.abc) + (reify* :implements [collections.abc/Sized] + (--len-- [this] 3) + (call [this] :called))""", + compiler.CompilerException, + ), + ( + """ + (import* abc collections.abc) + (do + (def Shape + (python/type "Shape" + #py (abc/ABC) + #py {"area" + (abc/abstractmethod + (fn []))})) + (reify* :implements [Shape] + (area [this] (* 2 1 1)) + (call [this] :called)))""", + runtime.RuntimeException, + ), + ], + ) + def test_reify_may_not_add_extra_methods_to_interface( + self, lcompile: CompileFn, code: str, ExceptionType, + ): + with pytest.raises(ExceptionType): + lcompile(code) + + @pytest.mark.parametrize( + "code", ["(reify* :implements [])", "(reify* :implements [python/object])"] + ) + def test_reify_interface_may_have_no_fields_or_methods( + self, lcompile: CompileFn, code: str, + ): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (fn [x y z] + (reify* :implements [python/object] + (__str__ [this] + (python/repr #py ("Point" x y z)))))""", + """ + (do + (def PyObject python/object) + (fn [x y z] + (reify* :implements [PyObject] + (__str__ [this] + (python/repr #py ("Point" x y z))))))""", + ], + ) + def test_reify_interface_may_implement_only_some_object_methods( + self, lcompile: CompileFn, code: str + ): + Point = lcompile(code) + pt = Point(1, 2, 3) + assert "('Point', 1, 2, 3)" == str(pt) + + @pytest.mark.parametrize( + "code", + [ + """ + (fn* [x y z] + (reify* :implements [WithProp] + (^:property prop [this] [x y z]) + (prop [self] self)))""", + """ + (fn* [x y z] + (reify* :implements [WithProp] + (prop [self] self) + (^:property prop [this] [x y z])))""", + ], + ) + def test_reify_property_and_method_names_cannot_overlap( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile( + f""" + (import* abc) + (def WithProp + (python/type "WithProp" + #py (abc/ABC) + #py {{"prop" + (python/property + (abc/abstractmethod + (fn [self])))}})) + (def WithMember + (python/type "WithMember" + #py (abc/ABC) + #py {{"prop" + (abc/abstractmethod + (fn [cls]))}})) + {code}""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* abc) + (def WithCls + (python/type "WithCls" + #py (abc/ABC) + #py {"create" + (python/classmethod + (abc/abstractmethod + (fn [self])))})) + (reify* :implements [WithProp] + (^:classmethod create [cls] cls))""", + """ + (import* abc) + (def WithStatic + (python/type "WithStatic" + #py (abc/ABC) + #py {"dostatic" + (python/staticmethod + (abc/abstractmethod + (fn [self])))})) + (reify* :implements [WithProp] + (^:staticmethod dostatic [] :staticboi))""", + ], + ) + def test_reify_disallows_class_and_static_members( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_transfers_form_meta_to_obj(self, lcompile: CompileFn): + make_obj = lcompile( + """ + (fn [x] + ^{:passed-through true} + (reify* :implements [python/object] + (--call-- [this] x)))""" + ) + o = make_obj(kw.keyword("x")) + assert isinstance(o, IWithMeta) + assert lmap.map({kw.keyword("passed-through"): True}) == o.meta() + assert kw.keyword("x") == o() + + new_meta = lmap.map({kw.keyword("replaced"): kw.keyword("yes")}) + new_o = o.with_meta(new_meta) + assert isinstance(o, IWithMeta) + assert new_meta == new_o.meta() + assert kw.keyword("x") == new_o() + + assert type(o) is type(new_o) + + class TestReifyMember: + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (:--call-- [this] [x y z])))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* collections.abc/Callable + (\"--call--\" [this] [x y z])))""", + ], + ) + def test_reify_member_is_named_by_sym(self, lcompile: CompileFn, code: str): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_member_args_are_vec( + self, lcompile: CompileFn, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (import* collections.abc) + (fn [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- (this) [x y z])))""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (^:property ^:staticmethod __call__ [this] + [x y z])))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* collections.abc/Callable + (^:classmethod ^:property __call__ [this] + [x y z])))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* collections.abc/Callable + (^:classmethod ^:staticmethod __call__ [this] + [x y z])))""", + ], + ) + def test_reify_member_may_not_be_multiple_types( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + class TestReifyMethod: + def test_reify_fields_and_methods(self, lcompile: CompileFn): + make_point = lcompile( + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable collections.abc/Sized] + (--len-- [this] 1) + (--call-- [this] [x y z])))""" + ) + pt = make_point(1, 2, 3) + assert 1 == len(pt) + assert vec.v(1, 2, 3) == pt() + + def test_reify_method_with_args(self, lcompile: CompileFn): + make_point = lcompile( + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this i j k] [x i y j z k])))""" + ) + pt = make_point(1, 2, 3) + assert vec.v(1, 4, 2, 5, 3, 6) == pt(4, 5, 6) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this &])))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this & :args])))""", + ], + ) + def test_reify_method_with_varargs_malformed( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_method_with_varargs(self, lcompile: CompileFn): + Mirror = lcompile( + """ + (import* collections.abc) + (fn* [x] + (reify* :implements [collections.abc/Callable] + (--call-- [this & args] [x args])))""" + ) + mirror = Mirror("Beauty is in the eye of the beholder") + assert vec.v( + "Beauty is in the eye of the beholder", llist.l(1, 2, 3) + ) == mirror(1, 2, 3) + + def test_reify_empty_method_body(self, lcompile: CompileFn): + Point = lcompile( + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this])))""" + ) + pt = Point(1, 2, 3) + assert None is pt() + + def test_reify_method_allows_recur(self, lcompile: CompileFn): + Point = lcompile( + """ + (import* collections.abc operator) + (fn* [x] + (reify* :implements [collections.abc/Callable] + (--call-- [this sum start] + (if (operator/gt start 0) + (recur (operator/add sum start) (operator/sub start 1)) + (operator/add sum x)))))""" + ) + pt = Point(7) + assert 22 == pt(0, 5) + + def test_reify_method_args_vec_includes_this(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [] [x y z])))""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [\"this\"] [x y z])))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this :new] [x y z])))""", + ], + ) + def test_reify_method_args_are_syms(self, lcompile: CompileFn, code: str): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_method_returns_value( + self, lcompile: CompileFn, + ): + Point = lcompile( + """ + (import* collections.abc) + (fn* [x] + (reify* :implements [collections.abc/Callable] + (--call-- [this new-val] + (* x new-val))))""" + ) + pt = Point(3) + assert 15 == pt(5) + + def test_reify_method_only_support_valid_kwarg_strategies( + self, lcompile: CompileFn + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (import* collections.abc) + (reify* :implements [collections.abc/Callable] + (^{:kwargs :kwarg-it} --call-- [this]))""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (^ {:kwargs :apply} --call-- + [this & args] + (merge {:x x :y y :z z} (apply hash-map args)))))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (^{:kwargs :collect} --call-- + [this kwargs] + (merge {:x x :y y :z z} kwargs))))""", + ], + ) + def test_reify_method_kwargs(self, lcompile: CompileFn, code: str): + Point = lcompile(code) + + pt = Point(1, 2, 3) + assert lmap.map( + { + kw.keyword("w"): 2, + kw.keyword("x"): 1, + kw.keyword("y"): 4, + kw.keyword("z"): 3, + } + ) == pt(w=2, y=4) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this] + :no-args) + (--call-- [this] + :also-no-args)))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this s] + :one-arg) + (--call-- [this s] + :also-one-arg)))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this] + :no-args) + (--call-- [this s] + :one-arg) + (--call-- [this a b] + [a b]) + (--call-- [this s3] + :also-one-arg)))""", + ], + ) + def test_no_reify_method_arity_has_same_fixed_arity( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this & args] + (concat [:no-starter] args)) + (--call-- [this s & args] + (concat [s] args))))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this s & args] + (concat [s] args)) + (--call-- [this & args] + (concat [:no-starter] args))))""", + ], + ) + def test_reify_method_cannot_have_two_variadic_arities( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_reify_method_variadic_method_cannot_have_lower_fixed_arity_than_other_methods( + self, lcompile: CompileFn, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this a b] + [a b]) + (--call-- [this & args] + (concat [:no-starter] args))))""" + ) + + @pytest.mark.parametrize( + "code", + [ + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this s] s) + (^{:kwargs :collect} --call-- [this s kwargs] + (concat [s] kwargs))))""", + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (^{:kwargs :collect} --call-- [this kwargs] kwargs) + (^{:kwargs :apply} --call-- [thi shead & kwargs] + (apply hash-map :first head kwargs))))""", + ], + ) + def test_reify_method_does_not_support_kwargs( + self, lcompile: CompileFn, code: str + ): + with pytest.raises(compiler.CompilerException): + lcompile(code) + + def test_multi_arity_reify_method_dispatches_properly( + self, lcompile: CompileFn, ns: runtime.Namespace, + ): + code = """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this] :a) + (--call-- [this s] [:a s])))""" + Point = lcompile(code) + assert callable(Point(1, 2, 3)) + assert kw.keyword("a") == Point(1, 2, 3)() + assert vec.v(kw.keyword("a"), kw.keyword("c")) == Point(1, 2, 3)( + kw.keyword("c") + ) + + code = """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this] :no-args) + (--call-- [this s] s) + (--call-- [this s & args] + (concat [s] args))))""" + Point = lcompile(code) + assert callable(Point(1, 2, 3)) + assert Point(1, 2, 3)() == kw.keyword("no-args") + assert Point(1, 2, 3)("STRING") == "STRING" + assert Point(1, 2, 3)(kw.keyword("first-arg"), "second-arg", 3) == llist.l( + kw.keyword("first-arg"), "second-arg", 3 + ) + + def test_multi_arity_reify_method_call_fails_if_no_valid_arity( + self, lcompile: CompileFn, + ): + Point = lcompile( + """ + (import* collections.abc) + (fn* [x y z] + (reify* :implements [collections.abc/Callable] + (--call-- [this] :send-me-an-arg!) + (--call-- [this i] i) + (--call-- [this i j] (concat [i] [j]))))""" + ) + + with pytest.raises(runtime.RuntimeException): + Point(1, 2, 3)(4, 5, 6) + + class TestReifyProperty: + @pytest.fixture(autouse=True) + def property_interface(self, lcompile: CompileFn): + return lcompile( + """ + (import* abc) + (def WithProp + (python/type "WithProp" + #py (abc/ABC) + #py {"prop" + (python/property + (abc/abstractmethod + (fn [self])))}))""" + ) + + @pytest.mark.parametrize( + "code,ExceptionType", + [ + ( + """ + (fn* [x y z] + (reify* :implements [WithProp]))""", + compiler.CompilerException, + ), + ( + """ + (do + (import* abc) + (def WithProperty + (python/type "WithProp" + #py (abc/ABC) + #py {"a_property" + (python/property + (abc/abstractmethod + (fn [self])))})) + (reify* :implements [WithProperty]))""", + runtime.RuntimeException, + ), + ], + ) + def test_reify_must_implement_interface_property( + self, lcompile: CompileFn, code: str, ExceptionType + ): + with pytest.raises(ExceptionType): + lcompile(code) + + def test_reify_property_includes_this( + self, lcompile: CompileFn, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (fn* [x y z] + (reify* :implements [WithProp] + (^:property prop [] [x y z])))""" + ) + + def test_reify_property_args_are_syms( + self, lcompile: CompileFn, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (fn* Point [x y z] + (reify* :implements [WithProp] + (^:property prop [:this] [x y z])))""" + ) + + def test_reify_property_may_not_have_args( + self, lcompile: CompileFn, + ): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (fn* [x y z] + (reify* :implements [WithProp] + (^:property prop [this and-that] [x y z])))""" + ) + + def test_reify_property_disallows_recur(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (fn* [x] + (reify* :implements [WithProp] + (^:property prop [this] + (recur))))""" + ) + + def test_reify_can_have_property( + self, lcompile: CompileFn, + ): + Point = lcompile( + """ + (fn* [x y z] + (reify* :implements [WithProp] + (^:property prop [this] [x y z])))""" + ) + assert vec.v(1, 2, 3) == Point(1, 2, 3).prop + + def test_reify_empty_property_body( + self, lcompile: CompileFn, + ): + Point = lcompile( + """ + (fn* [x y z] + (reify* :implements [WithProp] + (^:property prop [this])))""" + ) + assert None is Point(1, 2, 3).prop + + @pytest.mark.parametrize("kwarg_support", [":apply", ":collect", ":kwarg-it"]) + def test_reify_property_does_not_support_kwargs( + self, lcompile: CompileFn, kwarg_support: str + ): + with pytest.raises(compiler.CompilerException): + lcompile( + f""" + (fn* [x y z] + (reify* :implements [WithProp] + (^:property ^{{:kwargs {kwarg_support}}} prop [this])))""" + ) + + def test_reify_property_may_not_be_multi_arity(self, lcompile: CompileFn): + with pytest.raises(compiler.CompilerException): + lcompile( + """ + (fn* [x] + (reify* :implements [WithProp] + (^:property prop [this] :a) + (^:property prop [this] :b)))""" + ) + + class TestRequire: @pytest.mark.parametrize( "code",