diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_bindgen | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_bindgen')
200 files changed, 16787 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/.cargo-checksum.json b/third_party/rust/uniffi_bindgen/.cargo-checksum.json new file mode 100644 index 0000000000..880d79a96f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"5285a403f48ecf7a073cbe1839c7afbd0e14998e8315b1ff8ecefb9e7171fc4e","askama.toml":"1a245b7803adca782837e125c49100147d2de0d5a1c949ff95e91af1701f6058","src/backend/config.rs":"4861dbf251dbb10beb1ed7e3eea7d79499a0de1cd9ce9ee8381a0e729c097dea","src/backend/filters.rs":"2da4eaa9af92e449f2fa20d06fc2ab2f758a9a10d3ad6cb8a94975184d40d2ff","src/backend/mod.rs":"2ee9d974cd259f7fb156028b4f4f7601691e94fb5692a6daf0d362df3ecf79a8","src/backend/types.rs":"598df3a861f5d53b2c710848943f6049dd43cb4f37aa81f2c08fd36fc5b2f5d5","src/bindings/kotlin/gen_kotlin/callback_interface.rs":"a6c7796ca66cbaabeef401b939d3c707bba17a77581da36a3a0b46f87630440e","src/bindings/kotlin/gen_kotlin/compounds.rs":"ebd2111a74032b336e0768facfb756a9422da2f9b413ee929b24c1c4315e6a06","src/bindings/kotlin/gen_kotlin/custom.rs":"7e619f7320796ecd8c4ced82904b4bd3c6a0043b55d5829274ab3040cdf9cd7f","src/bindings/kotlin/gen_kotlin/enum_.rs":"6559bb00d8e359126b016e549263c0c9bc1dfc5654ed662c0c2912b47931b1e4","src/bindings/kotlin/gen_kotlin/executor.rs":"58a192123fd2dd4b625f29d95ae6bf5161c2fef7bf50aa8790c3ae0e7a9430d9","src/bindings/kotlin/gen_kotlin/external.rs":"bcd2a44f2559a124aa287944ab59296239033372c6b4a7a3b625b1d41c441de2","src/bindings/kotlin/gen_kotlin/miscellany.rs":"6541987e627c4ff27a17ebe919b2b5cd97cb66ff41187ed636396b4e35ea2414","src/bindings/kotlin/gen_kotlin/mod.rs":"ccae80314058df0b4988d0965ab62b0dc872e8676592e7b55037cd54011e6854","src/bindings/kotlin/gen_kotlin/object.rs":"539ec05386c1e844bef09d4de8374760daa5ba99b009615c04be9c3927feb4c9","src/bindings/kotlin/gen_kotlin/primitives.rs":"2c3020416384a67855ca5086e485c4db6d7dcc3ce51343217b4a914b318ae350","src/bindings/kotlin/gen_kotlin/record.rs":"96fd1a180095a062b4a9b71d4f603b232f0133f46355a3e427c4064701d900f2","src/bindings/kotlin/gen_kotlin/variant.rs":"d111d6888745195fc2c24bdddc57359e771616102a8d182c5c8ad268b0a13460","src/bindings/kotlin/mod.rs":"ef88eb9b5b7d6f920c62a525ea4d4bf2a3b1a9154afaa012cdb2feea597fbf23","src/bindings/kotlin/templates/Async.kt":"064ce385ac0e68719de625e0172908257714b62da296ff14b2c0153b9966212a","src/bindings/kotlin/templates/BooleanHelper.kt":"28e8a5088c8d58c9bfdbc575af8d8725060521fdd7d092684a8044b24ae567c7","src/bindings/kotlin/templates/ByteArrayHelper.kt":"6fbb424556f631beb7f28c4168c568ad840445496a29d5c8f40a9a591b1661b1","src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt":"29fab10a8f6b699471e793e8d53f5bed74803a8c433ff80ccef5f56cf742c54a","src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt":"3b063ea03959c95327b6082c7edc0db0df83ee8b9e8d643b92ca45cf4fda458a","src/bindings/kotlin/templates/CustomTypeTemplate.kt":"be9bdc716731f3935a4d48728e33bfeb4acd514f3719ddbb273adcd6fb4ab31f","src/bindings/kotlin/templates/DurationHelper.kt":"414a98161538a26f3a9b357353270c1f245ad6ceed99496aca7162cf473a92fd","src/bindings/kotlin/templates/EnumTemplate.kt":"865fb1badd1a128390903ab8d9f42f9208c6db0eac5e53b88207282176cfd67f","src/bindings/kotlin/templates/ErrorTemplate.kt":"394c0093c0c86a0f2a14cd5fa60a70ba9970c65448867b6aca86fc25cfe08a4c","src/bindings/kotlin/templates/ExternalTypeTemplate.kt":"40df49116f9ea227c9a64a4f45bb7c2e99275c62e93f75290808e2c930911fba","src/bindings/kotlin/templates/FfiConverterTemplate.kt":"aa22962aaa9f641d48ccf44cb56d9f8a7736cbfaa01e1a1656662cfe5dd5c1d7","src/bindings/kotlin/templates/Float32Helper.kt":"662d95af3b629d143fb4d47cb7e9aa26ed28a5f3846de0341e28b0f9fb08bc25","src/bindings/kotlin/templates/Float64Helper.kt":"a77d099fa7d91e8702c1700e7949ffb6aaba9c6e8041ff48bab34b8e1fc9a0aa","src/bindings/kotlin/templates/ForeignExecutorTemplate.kt":"09c63a67adb8c6cb807108f02d7695d3425401ea0cc51b582cfd469a322fcce0","src/bindings/kotlin/templates/Helpers.kt":"90a11ec576e82265ba0f95fc330053779aada5976477f0d4a6b38619da1282cf","src/bindings/kotlin/templates/Int16Helper.kt":"7f83c4a48e1f3b2a59a3ca6a2662be8bc9baf3a5a748b31223cb3f51721ef249","src/bindings/kotlin/templates/Int32Helper.kt":"e02e4702175554b09fd2dd6ac3089dcd2c395f08ec60e762159566a9c9889450","src/bindings/kotlin/templates/Int64Helper.kt":"7a6fd6ca486852c89399c699935a9dfa1c32b9356d9a965cfde532581f05d9fa","src/bindings/kotlin/templates/Int8Helper.kt":"0554545494b6b9a76ce94f9c1723f8cf4230a13076feb75d620b1c9ca1ac4668","src/bindings/kotlin/templates/MapTemplate.kt":"07d20d8cf58a4bca950ac22dbec5e3471ac6c18c3cca562e45628de827b03ccf","src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt":"22226bb8dde52f12c77b40bbb8f7ceb7dcef313950b8d42b2f5fe44777158bfc","src/bindings/kotlin/templates/ObjectRuntime.kt":"7f38f54a0c889d7534d23afdace6b87b6ced5c024a36b5450078a06e071caad8","src/bindings/kotlin/templates/ObjectTemplate.kt":"3f3baea52b6923446827ea1ee3f5160edfe81e00c11f61ea1f72dbc6b796a6d8","src/bindings/kotlin/templates/OptionalTemplate.kt":"c916c4545d37087ee01a6b6aef966928691d26be539c388fce608e9e3ff4b0e7","src/bindings/kotlin/templates/RecordTemplate.kt":"8d573856de75b55b985594ac4e4a6f08da931dce6b52420654b5bb080eec414f","src/bindings/kotlin/templates/RustBufferTemplate.kt":"002878ce9ce9924231e55853b86768fc3dec2caef6a28098f01c3edd739ca076","src/bindings/kotlin/templates/SequenceTemplate.kt":"477a0f6714af151ca58cfc7c4f2cf0e878d391dd9db4efe964f19d5ad4f544f0","src/bindings/kotlin/templates/StringHelper.kt":"ec0441da90a394616d0ba3492eca50602161fe42062bc4f60e9a23191e71e009","src/bindings/kotlin/templates/TimestampHelper.kt":"353c2890f06ad6dda238e9aebb4bdff7bb838e17e46abf351ed3ff1fbc4e6580","src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt":"888ef82e2637ab104f0821803666c77212b5d5940414f71e899f8f8968ffe572","src/bindings/kotlin/templates/Types.kt":"4d87ef529f666db532fb5355a339fd50be3edd6225a703564455b81ef4d4a16d","src/bindings/kotlin/templates/UInt16Helper.kt":"e84a1f30a5a899ba2c5db614d3f3c74f25bccf6dd99bf68b8830829332d051e9","src/bindings/kotlin/templates/UInt32Helper.kt":"7cdf08cc580046935f27ba07b53685968608a102e0a6be305111037c63d7ddf8","src/bindings/kotlin/templates/UInt64Helper.kt":"fd7baacbf3ab6202ff83edcc66e5f7beb11a10053ba66d0b49547616cc7cbe1f","src/bindings/kotlin/templates/UInt8Helper.kt":"bbf5a6d66c995aea9fe2fa9840c6bfa78b03520a09469b984f0e1d43191e453a","src/bindings/kotlin/templates/macros.kt":"0f64366a9d7523b3d20d7e9d8b04eb064568772dec529a12f878acf5d2246a41","src/bindings/kotlin/templates/wrapper.kt":"d515ca22d12f13b1b99c5daa411ea35d9a288076e4b7208eaf88170e5da7477c","src/bindings/kotlin/test.rs":"0f752ab0afde20194afca07af94da9d1422300032696d5f845cd864fc63c5d51","src/bindings/mod.rs":"949f323d6eb5c018497103dbb9dcffb8f395eb5960694b551a24b4887e853afb","src/bindings/python/gen_python/callback_interface.rs":"5df3e091d3c88ef7645e570f693942161a9b9c6307419c15a2534fbc5da974af","src/bindings/python/gen_python/compounds.rs":"9b7187d35826e1b12dbc2b16a13aec783a51f0952e3e2d24adaefbd0ac005016","src/bindings/python/gen_python/custom.rs":"81501641648eb638f5a338c01a71db0d0e96601c3dda83acdb2d49072b387d42","src/bindings/python/gen_python/enum_.rs":"7c3f8f6a97c1491175c8b93b8f9ab13748e2f8084bb717836b6935d024805439","src/bindings/python/gen_python/error.rs":"161bd2e041e3a63a91899de173eec8450cc10e1e9552d064969aa72a02fdfd5e","src/bindings/python/gen_python/executor.rs":"dbbf2292c79f73dacb317a8645185e42f328a017951662975c0488399c562058","src/bindings/python/gen_python/external.rs":"0325e9a39645eb5454d716d4db76a4a31083ddfafa8e9ca063257292372a0637","src/bindings/python/gen_python/miscellany.rs":"d6f6305dd0af85b7ba87b70cbe6ecba00c83d5082c5bdcaf25962fff853973ea","src/bindings/python/gen_python/mod.rs":"4ac0dc0fd9aaabf2e1f9245d13e0090ee0e9c1235ef203bec9314cac5974e9f0","src/bindings/python/gen_python/object.rs":"a4d4c20a0a52687feff2b9a547a13aa9bda09b3af9ec26508646658a88eec8b3","src/bindings/python/gen_python/primitives.rs":"b830c68e20d8539b8ac5566f1ca0dd262c1b14712a747f79e70004cd8f409ba1","src/bindings/python/gen_python/record.rs":"f8e12ce43d7e0f37f05420a849e7867b7251f9790933609a4cb99050fd063089","src/bindings/python/mod.rs":"eac32ce383460d58d3ccf1d406173465fc8a1db8a24408df67620b7d14dcd0cd","src/bindings/python/templates/Async.py":"4a35a878883a548f3bbed929a9ec74c133e2e9cf08375989503e73ddc2f9c648","src/bindings/python/templates/BooleanHelper.py":"c19e38ae3daa29a831f2394a0a2e74c924711e55ddc85db8ac9b5b8b6da9cd92","src/bindings/python/templates/BytesHelper.py":"e8fb9919acc784fb056bba4ab8d5c04ca7b2275211f8397ef2a391833e3d5e8f","src/bindings/python/templates/CallbackInterfaceRuntime.py":"795d8826d5d2b397a91c531c6b1b76d9425728efdcb90514170c8c4f35053e40","src/bindings/python/templates/CallbackInterfaceTemplate.py":"3f38e7b290fce198d188a63dcfa74486810153816283e683d6e04896c2dbea9a","src/bindings/python/templates/CustomType.py":"12064dde5e1baf4d78e541837c414cdc7ee9e827a284c54a98ac92dfaf3478e8","src/bindings/python/templates/DurationHelper.py":"271c301bc480cd48d5df2aec15789dd360f4d3098a9f360d7f8f33fa0a7fcd0a","src/bindings/python/templates/EnumTemplate.py":"1cbc2206f045c3050be1912df581a5393d6f0a4a79c96d8b49661696d25830e0","src/bindings/python/templates/ErrorTemplate.py":"f9ab6c910024e88ff92a7575d5d00cdafe448b2e85d07a9edea57ae7b6dc5864","src/bindings/python/templates/ExternalTemplate.py":"ed4d65caf2de3fd2c2a3fd2658eb44cf91cd2f0878c017be63afa4394bf56be3","src/bindings/python/templates/Float32Helper.py":"80c0a0619d2c58c100ea8db37125878c8e8cf56c42f77195aa9b4b6b6d5716c8","src/bindings/python/templates/Float64Helper.py":"62c3ed0d646d3383c890d1f8fd7cb8639433971b9ba9261ac43c1391472eb141","src/bindings/python/templates/ForeignExecutorTemplate.py":"6a7903acc65b9dac17524767d94b142d38d25d5f5bb27133ee9fd7ed8fb5f5a9","src/bindings/python/templates/Helpers.py":"3265eeb5917e0090a7dbd50fdbd12e7d1b1832a58b347cd003ecf8a433c9ebcd","src/bindings/python/templates/Int16Helper.py":"ef7fd0035a80aef556bdbfbcf074751d4e25661f4e07f9bf41f48601d171e5aa","src/bindings/python/templates/Int32Helper.py":"af7e0176ed41260089426498946e47565a7d57e98dffaae4562dfe541c3019d1","src/bindings/python/templates/Int64Helper.py":"171908319be9edcfe3b178d1d74f0173df2aae6a4a92895a2079fa476caed7df","src/bindings/python/templates/Int8Helper.py":"d02a4a5452ec1096b1b1953e4d661d699f9f8f0ce5086a6f3577a0119f479666","src/bindings/python/templates/MapTemplate.py":"fd0bd7e396a6288a16ecb3dae087ab725be556789887a4dc0c00ab97d815f3fb","src/bindings/python/templates/NamespaceLibraryTemplate.py":"b78b7161d43a95f5a0b5d7da6cae0d8bf47017c6c77ad210e15be9531413baa9","src/bindings/python/templates/ObjectTemplate.py":"3af0c737d1b482169d62dee1b6c49f1a18ea3f485e9fc323b070f35cf00c242c","src/bindings/python/templates/OptionalTemplate.py":"59df962441fb1c50cb99be014c87ecf69450c1a3b60fa6763bd40e9e948124b1","src/bindings/python/templates/PointerManager.py":"22faf6a2801cf756f3b09415b597f0cd403a3872ac99a7e44e3b7b6217606cd7","src/bindings/python/templates/RecordTemplate.py":"04fcbd662bc9817597366046e09321d2516eb8240e796dd9b6f971c347475429","src/bindings/python/templates/RustBufferHelper.py":"8a8c20d195534e465a173dc778ae98957c50e209ec824af2d2b143f5ba6061f5","src/bindings/python/templates/RustBufferTemplate.py":"f5e247b0f8988f29ce94ab50b5fe7749d0423cda1d37b6145cc8a6c5a9818449","src/bindings/python/templates/SequenceTemplate.py":"047f19074fe08982b59001da2ba7318b331ce431d73503e111330579a3ba065f","src/bindings/python/templates/StringHelper.py":"a3f874ea9330413e854c2ebbeff5507e32a166de6967c5cea63ede1f021e267a","src/bindings/python/templates/TimestampHelper.py":"de099ce51ceaa86519c28bb38e21933ead36ff341f4907695029212bcdfed3ac","src/bindings/python/templates/TopLevelFunctionTemplate.py":"93b6101fae2cafdf1a9325bed07019609ac35bacef2dc31ba4be5c256d827473","src/bindings/python/templates/Types.py":"feb69d895e9279e52479146e1dfe2fec48c547378ef2cc0fb988f6acaf6bcc63","src/bindings/python/templates/UInt16Helper.py":"5fbb30ece1f9a2680b60baf680ec4e2936d64de2ff107018e751ef1c041443ef","src/bindings/python/templates/UInt32Helper.py":"84207c380e38a38bb919d58769384a0f4fa175ebbd04ac451b37ccfe01ff68a1","src/bindings/python/templates/UInt64Helper.py":"4606f381834740319a9f604a418ba149917a6dbd43d3d3d8da50c655893e2c8e","src/bindings/python/templates/UInt8Helper.py":"aecf7cf08b7dc75fe81e8dcac78dfce166b132d5c20b4301d845c479ab9f49ba","src/bindings/python/templates/macros.py":"7d0d08f418edf65ff365bf1fb37e3132aebb720dfecdcef3e3ce50529fe0eb6b","src/bindings/python/templates/wrapper.py":"91e8cbf18e5b7d0b2be31c0e09b230318d19406c316b453dab973341eb2c6add","src/bindings/python/test.rs":"48e93959ce3e34ff0191126416301b170239d3e2665711da786e0b8b7a90a2ab","src/bindings/ruby/gen_ruby/mod.rs":"7f3a94537c331a941e6e010e35563247f11f1fedaf971cb8538e17797cb17efa","src/bindings/ruby/gen_ruby/tests.rs":"7dcb86b08e643c43503f4cac6396833497f6988b004321c0067700ee29ffbf32","src/bindings/ruby/mod.rs":"0fdfab5306dc5c05fbcbfb273340d96ad70c5caf5074834ad6851af1a9a56734","src/bindings/ruby/templates/EnumTemplate.rb":"5480edb347f5829e478d19474691babd72f37616ed846d519b5a61cb1d6cf047","src/bindings/ruby/templates/ErrorTemplate.rb":"301c177e639f0a27f19d4935c5317e672aadecbee2f9bfa778df982320f5148d","src/bindings/ruby/templates/Helpers.rb":"ce7ed4be97dad507b991c69c28dc7bb6427e5e79a4b2fba9dad9dccabc3e090c","src/bindings/ruby/templates/NamespaceLibraryTemplate.rb":"9a24c427b9eba99d9e13181a5559a385b5d1d16beae2b72a402f2645b22a9048","src/bindings/ruby/templates/ObjectTemplate.rb":"0cfd9438e4821cf2164b23d748b3227a8cffbe2fab5b7eb70832228ccb628ee0","src/bindings/ruby/templates/RecordTemplate.rb":"4aeff886928ca972e5dc9b799581b30c66a6f6dce446af3285dd3ed6b422dea9","src/bindings/ruby/templates/RustBufferBuilder.rb":"8da4e425b36dde4f171b238cbe57e02fb55e91a45a82134c1dccc0fc360733c0","src/bindings/ruby/templates/RustBufferStream.rb":"43ad2defc772fd24b68df0736533c26597ba007e89b6a5ba0d31fbe356648151","src/bindings/ruby/templates/RustBufferTemplate.rb":"405a32592cab145175b64e21398f83e6e0f16354552c0395479270e732910e08","src/bindings/ruby/templates/TopLevelFunctionTemplate.rb":"88213e7e25bef664da939c04dd5621f438af735ffcb4d2d0c24a529538630069","src/bindings/ruby/templates/macros.rb":"79d7d0e9af749dadbf242f37c0f86af7c616ea5318da127747def40f6cdb20d1","src/bindings/ruby/templates/wrapper.rb":"f82b41543546f8e5804cd0e1785f4735d9dd95383566d0e5ba1cd4d9e8c0578d","src/bindings/ruby/test.rs":"d19837119725233bd9971ca2dfc3256156071c64e6dfaf07ad2307432c055bbb","src/bindings/swift/gen_swift/callback_interface.rs":"6b51276350f506f96fefd0ae8cb3afdcd514e8a529d9e982afc68cbf68d74578","src/bindings/swift/gen_swift/compounds.rs":"1aba37cf2f438423a4ce476eea6a36f71f1d5daddbb77c88556bc3abde287ca7","src/bindings/swift/gen_swift/custom.rs":"bddb601b4ea8810ecbad01271d5ec0b3958999b09bc9382c83637dfd43451734","src/bindings/swift/gen_swift/enum_.rs":"87be67ec3394616368d9ef8e99b7f234c053b3bee9a7f9e6f2dff37f147c8837","src/bindings/swift/gen_swift/executor.rs":"ab672e2d05acbc2c4a839af22034aa557d5e69f1d9c913158310ea1e93851557","src/bindings/swift/gen_swift/external.rs":"321974136d58e649e60b2a3f70a369dce2d49f474f79579f8e0d66eb63d2d634","src/bindings/swift/gen_swift/miscellany.rs":"7fc2444596d76545ad82ee6c4bed64a29dd4a0438d50bfaafe511f41f6a0e409","src/bindings/swift/gen_swift/mod.rs":"41e4bf2fbe622d0dba85363455e287e00dc48e4043910cef35c30ce170acf52b","src/bindings/swift/gen_swift/object.rs":"2269f65a6b58a24bd08fedb133a38b37663bcf11d0586c50a67028022706a156","src/bindings/swift/gen_swift/primitives.rs":"c8346601008ac6a6d07f08ec7395182c45a4d86c163dc1d6d9c326c49f2acda1","src/bindings/swift/gen_swift/record.rs":"5ad98ab04a5d8178daf0956db819c87d26aae7bf968184e88d512e34c02feb90","src/bindings/swift/mod.rs":"26ba270cb7913661f3cee703038d1ea4a70bff64c3b31351d6bc77e67cdee20d","src/bindings/swift/templates/Async.swift":"84b9be2b5eca2dcfad7eee0cb8d34fec613a4bfdc8a7170b8d11575e457f567b","src/bindings/swift/templates/BooleanHelper.swift":"f02e391bed44ca0e03c66c4e6a1545caaae490fc72c4cf5935e66220082a8c30","src/bindings/swift/templates/BridgingHeaderTemplate.h":"3f468869e77b9293836822b8f2ac348716ab5d487f7b8fef1a87ec30ddafa8d5","src/bindings/swift/templates/CallbackInterfaceRuntime.swift":"2c71ac715ad0bca6f73559748453ba37ca242c90de38f76876989be05d21c49b","src/bindings/swift/templates/CallbackInterfaceTemplate.swift":"790f50b49d5b07dd44e8b215ed1fff02991c1f65aba9a9a9925275a544348813","src/bindings/swift/templates/CustomType.swift":"71520eb38a4be9035dca9e3e0402f386e7eaa79b28568bbc2f20d3fd53b4544d","src/bindings/swift/templates/DataHelper.swift":"df11547a2df57dcca0ff9cddc691bb5fa07d5ffd3d328d1c3b4443078008b111","src/bindings/swift/templates/DurationHelper.swift":"cbc41aaa58bda6c2313ede36a9f656a01a28f9c72aa1624e0e1c1da7b841ffb6","src/bindings/swift/templates/EnumTemplate.swift":"fe205dd28defea8ed6126a45b2a95240a920dfebda8927134a50c3b6d0d7e9d7","src/bindings/swift/templates/ErrorTemplate.swift":"3dddb278763b75b38294c1165522fa91078a951ce05c91fbdfde43b5a097f34f","src/bindings/swift/templates/Float32Helper.swift":"ea32538058c4b3c72b1cd2530ac00d0349fadab5e1bc617d33aae4c87692fc98","src/bindings/swift/templates/Float64Helper.swift":"e27e9424dc6e97b8cacc6ca4c796dd2d16dcfcb877e2f19c45eca03381a41e78","src/bindings/swift/templates/ForeignExecutorTemplate.swift":"205933825e691fec525286d263ea34d592cc462257764ee76325bf98cb3cd240","src/bindings/swift/templates/Helpers.swift":"a88fd909787b855998671e551cdb3284109e2fd2b2e7492b1c93c82aad0e9d35","src/bindings/swift/templates/Int16Helper.swift":"204906911813a3931436057c23032f4c4e39e023df90d641d6c6086aefe2f820","src/bindings/swift/templates/Int32Helper.swift":"0997f059c9c4edd3c41aee0bbad4aa2bda6d791a0d623ad8014d5aa6bdae718d","src/bindings/swift/templates/Int64Helper.swift":"bcf8c2deebb3ee9bce87735adc4bd100981989943b69f6a7fb499a9aec4c25d9","src/bindings/swift/templates/Int8Helper.swift":"ad1ec0fa213724933fa4dc4e2e304e13ac4722b774bfffac44793986b997dd33","src/bindings/swift/templates/MapTemplate.swift":"53971ec388417b02519f8deb8d66361ab4693eae77d116f6051cbea4738054ec","src/bindings/swift/templates/ModuleMapTemplate.modulemap":"99ad1e9bf550a21497296f9248ecd4385dd6d0b5892951d24cf990cdbf3eec2c","src/bindings/swift/templates/ObjectTemplate.swift":"4633572ac6d27a0f82f3b125c2136ad4fa126391c0b64b5db1bde4b04a77f807","src/bindings/swift/templates/OptionalTemplate.swift":"2376487ceadca3975f0e82ddf6ce61af8bbbf5b0592fa9cd977460f148d8c99d","src/bindings/swift/templates/RecordTemplate.swift":"16e0b98354b624a8922d7d384a005fa660a6a388d70381872ebbcd0de9fb78a4","src/bindings/swift/templates/RustBufferTemplate.swift":"f4422fdf0cb5b4db267d461f063dedc319ea1a5a13bae1b82c3f108ba8c658bb","src/bindings/swift/templates/SequenceTemplate.swift":"8425b279197582a94b4cf363ab0463907a68a624e24045720ef7df2bcacf3383","src/bindings/swift/templates/StringHelper.swift":"968b9b9b7fbe06a2ac2143016edaff3552e201652accb8d613b03645f0d24a90","src/bindings/swift/templates/TimestampHelper.swift":"82eece13aa186c8e3745c8ad2f1290639ca9689573018a2bdc5c75afbae58c26","src/bindings/swift/templates/TopLevelFunctionTemplate.swift":"ffe0287861e67dcad2be77f30c00c0a326ab59ffbd37409de3bafc969d49df26","src/bindings/swift/templates/Types.swift":"98c654bfc5d2d4ece965cfe15b00e7151b815e26bfb55abf17e799efffccc2c0","src/bindings/swift/templates/UInt16Helper.swift":"d6fba577324fc0e9e50329c968df99341de418011be126bd29702f8a94d87c02","src/bindings/swift/templates/UInt32Helper.swift":"5e0cf151a0c40098b3d96815ba3de23e15fe52f3c517577e1b0c7e7a4c12428f","src/bindings/swift/templates/UInt64Helper.swift":"17237b38d09ced8d2a8ff1ad9ca86873a19e417280e0e60f33d7063397ea4b7b","src/bindings/swift/templates/UInt8Helper.swift":"c4cb2ee4a78b54ca8d0013383c6b43e9ecd42776e3dc7d6e40086325d41714e5","src/bindings/swift/templates/macros.swift":"438091831b355b0ba6726dab7c17c0687ca58854c7799e946a8012e95a42f4ba","src/bindings/swift/templates/wrapper.swift":"d83a1b8ac3ffc761d4e560adae57d5ad275e0fd1bf3fdc35f3b3b3699317c6e6","src/bindings/swift/test.rs":"c15d19e7f324613e2dbd7995dffba875ed430919a0882f05e8e1f6cc8aea613c","src/interface/callbacks.rs":"34384f1a4e89cd30e2c35beab2bbd8cc0a9e3dd21ed0b390859610fbe209b16a","src/interface/enum_.rs":"3a4f9e77d80128444a8a226ef1e5ad7cd339f8d815aa7012554cf94c2e4edd98","src/interface/ffi.rs":"fa23a2e6fcd89d956523bd0aa5c946138ac629752f0be9085e09f78aade75fac","src/interface/function.rs":"835ee542cb19c67fb95502a790771e54deb712272baed8bdc3f6bfabd9c69d6f","src/interface/mod.rs":"5bd3b5a2dd91382089323a21d4d0419dd5621799add4786a4710df0afebcca69","src/interface/object.rs":"2364c4e7f887d6e4256d065f9ad049ffad19f92fe1c1d76789c33a22376bdd7f","src/interface/record.rs":"931dbaa8cb440debfcf14d51be3e6c517ba2c28655033ad2486e384a9bea25c4","src/interface/universe.rs":"66deaa33394e401e3005670731a0685068302fc01640655335a7cbe9fb4cf48e","src/lib.rs":"64a6239dbaa8196089600e7b0c577b43eefc4a9935893292298bf400fd77a03d","src/library_mode.rs":"89c9fc47db09ccb779fd2842d3c7e07b7863164bb5585eeaf959490ca5555438","src/macro_metadata/ci.rs":"07482b87bf5912277d114b24ad3e560cac94ca0087bd6ee9bee2c328d992bb18","src/macro_metadata/extract.rs":"a106a5b6ad8e5deba474646162b83ec4065796c58c60fc13dfbf1a72ed713833","src/macro_metadata/mod.rs":"bcb5e9a015510e9d74c288da928a4bfb8d80926a8ff85227c0eae8cdb2605519","src/scaffolding/mod.rs":"65749e72181c63edae55d49493aa49a1efece93c0a27deda230f5de4b9ba7d60","src/scaffolding/templates/CallbackInterfaceTemplate.rs":"0581f257c0eb7f0be922ef3ebf7892aafad15e9dc7865e53fc9396375f3188fb","src/scaffolding/templates/Checksums.rs":"ee926e840875c2e48e1d0cc5185c11f7a1ed3bde5264b07540812cb13c1d7481","src/scaffolding/templates/EnumTemplate.rs":"350cdcb4f23be6e6e2b442e0d4ea65549bb35a407bef1ac745a37a2e2b527fae","src/scaffolding/templates/ErrorTemplate.rs":"b93e8bc08d818fbf8976b766635ae192042b78c85f8086d4ee41ae03ab7f3b6f","src/scaffolding/templates/ExternalTypesTemplate.rs":"6cf8a89d9e6f1b6f5af4bc86e49454323fa4981846ec76d6661fcff41c348a8e","src/scaffolding/templates/ObjectTemplate.rs":"df614156bee529fd28905a6ad2270685e51736eade6af856edba5de6e9484141","src/scaffolding/templates/RecordTemplate.rs":"5b0f351739d5770f874fb7da56700c557df1d73ac219f3b18c4212d26fc422e0","src/scaffolding/templates/ReexportUniFFIScaffolding.rs":"aa8a1ffa98b6033707d965f90b5709474ed6bc79486fb47dacae8417fc056cf8","src/scaffolding/templates/TopLevelFunctionTemplate.rs":"96b99b38d074492673797737ddac0683c803a3908e03cc9b999d16f7a76ed178","src/scaffolding/templates/UdlMetadata.rs":"d7c50af1de92ef85630b385a910c7b29875502d622eb90da5541a7012b93d9e2","src/scaffolding/templates/macros.rs":"ea6bacd8dd9116ad739bdafe893d70407050f35e4a7ac8dd2c78b8ef34263e8e","src/scaffolding/templates/scaffolding_template.rs":"c8e18306a73ec5b764f665660fc5c91d498b63b6c3f489e524b2bae50f81f231"},"package":"fd992f2929a053829d5875af1eff2ee3d7a7001cb3b9a46cc7895f2caede6940"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_bindgen/Cargo.toml b/third_party/rust/uniffi_bindgen/Cargo.toml new file mode 100644 index 0000000000..9469a9cf25 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/Cargo.toml @@ -0,0 +1,81 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "uniffi_bindgen" +version = "0.25.3" +authors = ["Firefox Sync Team <sync-team@mozilla.com>"] +description = "a multi-language bindings generator for rust (codegen and cli tooling)" +homepage = "https://mozilla.github.io/uniffi-rs" +documentation = "https://mozilla.github.io/uniffi-rs" +keywords = [ + "ffi", + "bindgen", +] +license = "MPL-2.0" +repository = "https://github.com/mozilla/uniffi-rs" + +[dependencies.anyhow] +version = "1" + +[dependencies.askama] +version = "0.12" +features = ["config"] +default-features = false + +[dependencies.camino] +version = "1.0.8" + +[dependencies.cargo_metadata] +version = "0.15" + +[dependencies.clap] +version = "4" +features = [ + "std", + "derive", +] +optional = true +default-features = false + +[dependencies.fs-err] +version = "2.7.0" + +[dependencies.glob] +version = "0.3" + +[dependencies.goblin] +version = "0.6" + +[dependencies.heck] +version = "0.4" + +[dependencies.once_cell] +version = "1.12" + +[dependencies.paste] +version = "1.0" + +[dependencies.serde] +version = "1" + +[dependencies.toml] +version = "0.5" + +[dependencies.uniffi_meta] +version = "=0.25.3" + +[dependencies.uniffi_testing] +version = "=0.25.3" + +[dependencies.uniffi_udl] +version = "=0.25.3" diff --git a/third_party/rust/uniffi_bindgen/askama.toml b/third_party/rust/uniffi_bindgen/askama.toml new file mode 100644 index 0000000000..a0aae5f41b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/askama.toml @@ -0,0 +1,21 @@ +[general] +# Directories to search for templates, relative to the crate root. +dirs = [ "src/scaffolding/templates", "src/bindings/kotlin/templates", "src/bindings/python/templates", "src/bindings/swift/templates", "src/bindings/ruby/templates" ] + +[[syntax]] +name = "kt" + +[[syntax]] +name = "py" + +[[syntax]] +name = "swift" + +[[syntax]] +name = "c" + +[[syntax]] +name = "rs" + +[[syntax]] +name = "rb" diff --git a/third_party/rust/uniffi_bindgen/src/backend/config.rs b/third_party/rust/uniffi_bindgen/src/backend/config.rs new file mode 100644 index 0000000000..616cde0656 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/backend/config.rs @@ -0,0 +1,17 @@ +/* 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/. */ + +use serde::{Deserialize, Serialize}; + +/// Config value for template expressions +/// +/// These are strings that support simple template substitution. `{}` gets replaced by a value. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct TemplateExpression(String); + +impl TemplateExpression { + pub fn render(&self, var: &str) -> String { + self.0.replace("{}", var) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/backend/filters.rs b/third_party/rust/uniffi_bindgen/src/backend/filters.rs new file mode 100644 index 0000000000..0d2da8cab2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/backend/filters.rs @@ -0,0 +1,76 @@ +/* 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/. */ + +//! Backend-agnostic askama filters + +use crate::interface::{ + AsType, CallbackInterface, ComponentInterface, Enum, FfiType, Function, Object, Record, +}; +use askama::Result; +use std::fmt; + +// Need to define an error that implements std::error::Error, which neither String nor +// anyhow::Error do. +#[derive(Debug)] +struct UniFFIError { + message: String, +} + +impl UniFFIError { + fn new(message: String) -> Self { + Self { message } + } +} + +impl fmt::Display for UniFFIError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +impl std::error::Error for UniFFIError {} + +macro_rules! lookup_error { + ($($args:tt)*) => { + askama::Error::Custom(Box::new(UniFFIError::new(format!($($args)*)))) + } +} + +/// Get an Enum definition by name +pub fn get_enum_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Enum> { + ci.get_enum_definition(name) + .ok_or_else(|| lookup_error!("enum {name} not found")) +} + +/// Get a Record definition by name +pub fn get_record_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Record> { + ci.get_record_definition(name) + .ok_or_else(|| lookup_error!("record {name} not found")) +} + +/// Get a Function definition by name +pub fn get_function_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Function> { + ci.get_function_definition(name) + .ok_or_else(|| lookup_error!("function {name} not found")) +} + +/// Get an Object definition by name +pub fn get_object_definition<'a>(ci: &'a ComponentInterface, name: &str) -> Result<&'a Object> { + ci.get_object_definition(name) + .ok_or_else(|| lookup_error!("object {name} not found")) +} + +/// Get an Callback Interface definition by name +pub fn get_callback_interface_definition<'a>( + ci: &'a ComponentInterface, + name: &str, +) -> Result<&'a CallbackInterface> { + ci.get_callback_interface_definition(name) + .ok_or_else(|| lookup_error!("callback interface {name} not found")) +} + +/// Get the FfiType for a Type +pub fn ffi_type(type_: &impl AsType) -> Result<FfiType, askama::Error> { + Ok(type_.as_type().into()) +} diff --git a/third_party/rust/uniffi_bindgen/src/backend/mod.rs b/third_party/rust/uniffi_bindgen/src/backend/mod.rs new file mode 100644 index 0000000000..6abcfc10d7 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/backend/mod.rs @@ -0,0 +1,11 @@ +/* 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/. */ + +mod config; +pub mod filters; +mod types; + +pub use crate::interface::{Literal, Type}; +pub use config::TemplateExpression; +pub use types::CodeType; diff --git a/third_party/rust/uniffi_bindgen/src/backend/types.rs b/third_party/rust/uniffi_bindgen/src/backend/types.rs new file mode 100644 index 0000000000..3f66fdfe01 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/backend/types.rs @@ -0,0 +1,78 @@ +/* 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/. */ + +//! # Backend traits +//! +//! A trait to help format items. +//! +//! Each backend will have its own `filter` module, which is used by the askama templates. +use super::Literal; +use std::fmt::Debug; + +// XXX - Note that this trait is not used internally. It exists just to avoid an unnecessary +// breaking change for external bindings which use this trait. +// It is likely to be removed some time after 0.26.x. + +/// A Trait to help render types in a language specific format. +pub trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String { + self.type_label() + } + + fn literal(&self, _literal: &Literal) -> String { + unimplemented!("Unimplemented for {}", self.type_label()) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + /// Depending on the binding this will either be a singleton or a class with static methods. + /// + /// This is the newer way of handling these methods and replaces the lower, write, lift, and + /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other + /// backends to using this. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + /// An expression for lowering a value into something we can pass over the FFI. + fn lower(&self) -> String { + format!("{}.lower", self.ffi_converter_name()) + } + + /// An expression for writing a value into a byte buffer. + fn write(&self) -> String { + format!("{}.write", self.ffi_converter_name()) + } + + /// An expression for lifting a value from something we received over the FFI. + fn lift(&self) -> String { + format!("{}.lift", self.ffi_converter_name()) + } + + /// An expression for reading a value from a byte buffer. + fn read(&self) -> String { + format!("{}.read", self.ffi_converter_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option<Vec<String>> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option<String> { + None + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs new file mode 100644 index 0000000000..e20020e87c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn initialization_fn(&self) -> Option<String> { + Some(format!("{}.register", self.ffi_converter_name())) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs new file mode 100644 index 0000000000..4329f32f4c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs @@ -0,0 +1,98 @@ +/* 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/. */ + +use super::{AsCodeType, CodeType}; +use crate::backend::{Literal, Type}; +use crate::ComponentInterface; +use paste::paste; + +fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String { + match literal { + Literal::Null => "null".into(), + Literal::EmptySequence => "listOf()".into(), + Literal::EmptyMap => "mapOf()".into(), + + // For optionals + _ => super::KotlinCodeOracle.find(inner).literal(literal, ci), + } +} + +macro_rules! impl_code_type_for_compound { + ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { + paste! { + #[derive(Debug)] + pub struct $T { + inner: Type, + } + + impl $T { + pub fn new(inner: Type) -> Self { + Self { inner } + } + fn inner(&self) -> &Type { + &self.inner + } + } + + impl CodeType for $T { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci)) + } + + fn canonical_name(&self) -> String { + format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name()) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(literal, self.inner(), ci) + } + } + } + } + } + +impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}"); +impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}"); + +#[derive(Debug)] +pub struct MapCodeType { + key: Type, + value: Type, +} + +impl MapCodeType { + pub fn new(key: Type, value: Type) -> Self { + Self { key, value } + } + + fn key(&self) -> &Type { + &self.key + } + + fn value(&self) -> &Type { + &self.value + } +} + +impl CodeType for MapCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!( + "Map<{}, {}>", + super::KotlinCodeOracle.find(self.key()).type_label(ci), + super::KotlinCodeOracle.find(self.value()).type_label(ci), + ) + } + + fn canonical_name(&self) -> String { + format!( + "Map{}{}", + self.key().as_codetype().canonical_name(), + self.value().as_codetype().canonical_name(), + ) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(literal, &self.value, ci) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs new file mode 100644 index 0000000000..137cd0d8d9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/custom.rs @@ -0,0 +1,27 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + CustomCodeType { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self, _ci: &ComponentInterface) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs new file mode 100644 index 0000000000..f5300c10ee --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/enum_.rs @@ -0,0 +1,40 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + if let Literal::Enum(v, _) = literal { + format!( + "{}.{}", + self.type_label(ci), + super::KotlinCodeOracle.enum_variant_name(v) + ) + } else { + unreachable!(); + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs new file mode 100644 index 0000000000..154e12a381 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs @@ -0,0 +1,24 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct ForeignExecutorCodeType; + +impl CodeType for ForeignExecutorCodeType { + fn type_label(&self, _ci: &ComponentInterface) -> String { + // Kotlin uses a CoroutineScope for ForeignExecutor + "CoroutineScope".into() + } + + fn canonical_name(&self) -> String { + "ForeignExecutor".into() + } + + fn initialization_fn(&self) -> Option<String> { + Some("FfiConverterForeignExecutor.register".into()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs new file mode 100644 index 0000000000..3ecf09d47f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs @@ -0,0 +1,27 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + Self { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self, _ci: &ComponentInterface) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs new file mode 100644 index 0000000000..17331ff511 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/miscellany.rs @@ -0,0 +1,30 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; +use paste::paste; + +macro_rules! impl_code_type_for_miscellany { + ($T:ty, $class_name:literal, $canonical_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _ci: &ComponentInterface) -> String { + $class_name.into() + } + + fn canonical_name(&self) -> String { + $canonical_name.into() + } + } + } + }; +} + +impl_code_type_for_miscellany!(TimestampCodeType, "java.time.Instant", "Timestamp"); + +impl_code_type_for_miscellany!(DurationCodeType, "java.time.Duration", "Duration"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs new file mode 100644 index 0000000000..1ed0575a9a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -0,0 +1,530 @@ +/* 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/. */ + +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; + +use anyhow::{Context, Result}; +use askama::Template; +use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; +use serde::{Deserialize, Serialize}; + +use crate::backend::TemplateExpression; +use crate::interface::*; +use crate::BindingsConfig; + +mod callback_interface; +mod compounds; +mod custom; +mod enum_; +mod executor; +mod external; +mod miscellany; +mod object; +mod primitives; +mod record; +mod variant; + +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self, ci: &ComponentInterface) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String; + + fn literal(&self, _literal: &Literal, ci: &ComponentInterface) -> String { + unimplemented!("Unimplemented for {}", self.type_label(ci)) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + /// Depending on the binding this will either be a singleton or a class with static methods. + /// + /// This is the newer way of handling these methods and replaces the lower, write, lift, and + /// read CodeType methods. Currently only used by Kotlin, but the plan is to move other + /// backends to using this. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option<Vec<String>> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option<String> { + None + } +} + +// config options to customize the generated Kotlin. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Config { + package_name: Option<String>, + cdylib_name: Option<String>, + #[serde(default)] + custom_types: HashMap<String, CustomTypeConfig>, + #[serde(default)] + external_packages: HashMap<String, String>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CustomTypeConfig { + imports: Option<Vec<String>>, + type_name: Option<String>, + into_custom: TemplateExpression, + from_custom: TemplateExpression, +} + +impl Config { + pub fn package_name(&self) -> String { + if let Some(package_name) = &self.package_name { + package_name.clone() + } else { + "uniffi".into() + } + } + + pub fn cdylib_name(&self) -> String { + if let Some(cdylib_name) = &self.cdylib_name { + cdylib_name.clone() + } else { + "uniffi".into() + } + } +} + +impl BindingsConfig for Config { + fn update_from_ci(&mut self, ci: &ComponentInterface) { + self.package_name + .get_or_insert_with(|| format!("uniffi.{}", ci.namespace())); + self.cdylib_name + .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); + } + + fn update_from_cdylib_name(&mut self, cdylib_name: &str) { + self.cdylib_name + .get_or_insert_with(|| cdylib_name.to_string()); + } + + fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) { + for (crate_name, config) in config_map { + if !self.external_packages.contains_key(crate_name) { + self.external_packages + .insert(crate_name.to_string(), config.package_name()); + } + } + } +} + +// Generate kotlin bindings for the given ComponentInterface, as a string. +pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> { + KotlinWrapper::new(config.clone(), ci) + .render() + .context("failed to render kotlin bindings") +} + +/// A struct to record a Kotlin import statement. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum ImportRequirement { + /// The name we are importing. + Import { name: String }, + /// Import the name with the specified local name. + ImportAs { name: String, as_name: String }, +} + +impl ImportRequirement { + /// Render the Kotlin import statement. + fn render(&self) -> String { + match &self { + ImportRequirement::Import { name } => format!("import {name}"), + ImportRequirement::ImportAs { name, as_name } => { + format!("import {name} as {as_name}") + } + } + } +} + +/// Renders Kotlin helper code for all types +/// +/// This template is a bit different than others in that it stores internal state from the render +/// process. Make sure to only call `render()` once. +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "Types.kt")] +pub struct TypeRenderer<'a> { + config: &'a Config, + ci: &'a ComponentInterface, + // Track included modules for the `include_once()` macro + include_once_names: RefCell<HashSet<String>>, + // Track imports added with the `add_import()` macro + imports: RefCell<BTreeSet<ImportRequirement>>, +} + +impl<'a> TypeRenderer<'a> { + fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { + Self { + config, + ci, + include_once_names: RefCell::new(HashSet::new()), + imports: RefCell::new(BTreeSet::new()), + } + } + + // Get the package name for an external type + fn external_type_package_name(&self, module_path: &str, namespace: &str) -> String { + // config overrides are keyed by the crate name, default fallback is the namespace. + let crate_name = module_path.split("::").next().unwrap(); + match self.config.external_packages.get(crate_name) { + Some(name) => name.clone(), + // unreachable in library mode - all deps are in our config with correct namespace. + None => format!("uniffi.{namespace}"), + } + } + + // The following methods are used by the `Types.kt` macros. + + // Helper for the including a template, but only once. + // + // The first time this is called with a name it will return true, indicating that we should + // include the template. Subsequent calls will return false. + fn include_once_check(&self, name: &str) -> bool { + self.include_once_names + .borrow_mut() + .insert(name.to_string()) + } + + // Helper to add an import statement + // + // Call this inside your template to cause an import statement to be added at the top of the + // file. Imports will be sorted and de-deuped. + // + // Returns an empty string so that it can be used inside an askama `{{ }}` block. + fn add_import(&self, name: &str) -> &str { + self.imports.borrow_mut().insert(ImportRequirement::Import { + name: name.to_owned(), + }); + "" + } + + // Like add_import, but arranges for `import name as as_name` + fn add_import_as(&self, name: &str, as_name: &str) -> &str { + self.imports + .borrow_mut() + .insert(ImportRequirement::ImportAs { + name: name.to_owned(), + as_name: as_name.to_owned(), + }); + "" + } +} + +#[derive(Template)] +#[template(syntax = "kt", escape = "none", path = "wrapper.kt")] +pub struct KotlinWrapper<'a> { + config: Config, + ci: &'a ComponentInterface, + type_helper_code: String, + type_imports: BTreeSet<ImportRequirement>, + has_async_fns: bool, +} + +impl<'a> KotlinWrapper<'a> { + pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { + let type_renderer = TypeRenderer::new(&config, ci); + let type_helper_code = type_renderer.render().unwrap(); + let type_imports = type_renderer.imports.into_inner(); + Self { + config, + ci, + type_helper_code, + type_imports, + has_async_fns: ci.has_async_fns(), + } + } + + pub fn initialization_fns(&self) -> Vec<String> { + self.ci + .iter_types() + .map(|t| KotlinCodeOracle.find(t)) + .filter_map(|ct| ct.initialization_fn()) + .chain( + self.has_async_fns + .then(|| "uniffiRustFutureContinuationCallback.register".into()), + ) + .collect() + } + + pub fn imports(&self) -> Vec<ImportRequirement> { + self.type_imports.iter().cloned().collect() + } +} + +#[derive(Clone)] +pub struct KotlinCodeOracle; + +impl KotlinCodeOracle { + fn find(&self, type_: &Type) -> Box<dyn CodeType> { + type_.clone().as_type().as_codetype() + } + + /// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc). + fn class_name(&self, ci: &ComponentInterface, nm: &str) -> String { + let name = nm.to_string().to_upper_camel_case(); + // fixup errors. + ci.is_name_used_as_error(nm) + .then(|| self.convert_error_suffix(&name)) + .unwrap_or(name) + } + + fn convert_error_suffix(&self, nm: &str) -> String { + match nm.strip_suffix("Error") { + None => nm.to_string(), + Some(stripped) => format!("{stripped}Exception"), + } + } + + /// Get the idiomatic Kotlin rendering of a function name. + fn fn_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Kotlin rendering of a variable name. + fn var_name(&self, nm: &str) -> String { + format!("`{}`", nm.to_string().to_lower_camel_case()) + } + + /// Get the idiomatic Kotlin rendering of an individual enum variant. + fn enum_variant_name(&self, nm: &str) -> String { + nm.to_string().to_shouty_snake_case() + } + + fn ffi_type_label_by_value(ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)), + _ => Self::ffi_type_label(ffi_type), + } + } + + fn ffi_type_label(ffi_type: &FfiType) -> String { + match ffi_type { + // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not + // support them yet. Thus, we use the signed variants to represent both signed and unsigned + // types from the component API. + FfiType::Int8 | FfiType::UInt8 => "Byte".to_string(), + FfiType::Int16 | FfiType::UInt16 => "Short".to_string(), + FfiType::Int32 | FfiType::UInt32 => "Int".to_string(), + FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), + FfiType::Float32 => "Float".to_string(), + FfiType::Float64 => "Double".to_string(), + FfiType::RustArcPtr(_) => "Pointer".to_string(), + FfiType::RustBuffer(maybe_suffix) => { + format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) + } + FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), + FfiType::ForeignCallback => "ForeignCallback".to_string(), + FfiType::ForeignExecutorHandle => "USize".to_string(), + FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), + FfiType::RustFutureHandle => "Pointer".to_string(), + FfiType::RustFutureContinuationCallback => { + "UniFffiRustFutureContinuationCallbackType".to_string() + } + FfiType::RustFutureContinuationData => "USize".to_string(), + } + } +} + +trait AsCodeType { + fn as_codetype(&self) -> Box<dyn CodeType>; +} + +impl<T: AsType> AsCodeType for T { + fn as_codetype(&self) -> Box<dyn CodeType> { + // Map `Type` instances to a `Box<dyn CodeType>` for that type. + // + // There is a companion match in `templates/Types.kt` which performs a similar function for the + // template code. + // + // - When adding additional types here, make sure to also add a match arm to the `Types.kt` template. + // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + match self.as_type() { + Type::UInt8 => Box::new(primitives::UInt8CodeType), + Type::Int8 => Box::new(primitives::Int8CodeType), + Type::UInt16 => Box::new(primitives::UInt16CodeType), + Type::Int16 => Box::new(primitives::Int16CodeType), + Type::UInt32 => Box::new(primitives::UInt32CodeType), + Type::Int32 => Box::new(primitives::Int32CodeType), + Type::UInt64 => Box::new(primitives::UInt64CodeType), + Type::Int64 => Box::new(primitives::Int64CodeType), + Type::Float32 => Box::new(primitives::Float32CodeType), + Type::Float64 => Box::new(primitives::Float64CodeType), + Type::Boolean => Box::new(primitives::BooleanCodeType), + Type::String => Box::new(primitives::StringCodeType), + Type::Bytes => Box::new(primitives::BytesCodeType), + + Type::Timestamp => Box::new(miscellany::TimestampCodeType), + Type::Duration => Box::new(miscellany::DurationCodeType), + + Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), + Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), + Type::CallbackInterface { name, .. } => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) + } + Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), + Type::Optional { inner_type } => { + Box::new(compounds::OptionalCodeType::new(*inner_type)) + } + Type::Sequence { inner_type } => { + Box::new(compounds::SequenceCodeType::new(*inner_type)) + } + Type::Map { + key_type, + value_type, + } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), + } + } +} + +mod filters { + use super::*; + pub use crate::backend::filters::*; + + pub(super) fn type_name( + as_ct: &impl AsCodeType, + ci: &ComponentInterface, + ) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().type_label(ci)) + } + + pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().canonical_name()) + } + + pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().ffi_converter_name()) + } + + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!( + "{}.lower", + as_ct.as_codetype().ffi_converter_name() + )) + } + + pub(super) fn allocation_size_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!( + "{}.allocationSize", + as_ct.as_codetype().ffi_converter_name() + )) + } + + pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!( + "{}.write", + as_ct.as_codetype().ffi_converter_name() + )) + } + + pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.lift", as_ct.as_codetype().ffi_converter_name())) + } + + pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.read", as_ct.as_codetype().ffi_converter_name())) + } + + pub fn render_literal( + literal: &Literal, + as_ct: &impl AsType, + ci: &ComponentInterface, + ) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().literal(literal, ci)) + } + + pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) + } + + /// Get the idiomatic Kotlin rendering of a function name. + pub fn fn_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.fn_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of a variable name. + pub fn var_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.var_name(nm)) + } + + /// Get a String representing the name used for an individual enum variant. + pub fn variant_name(v: &Variant) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.enum_variant_name(v.name())) + } + + pub fn error_variant_name(v: &Variant) -> Result<String, askama::Error> { + let name = v.name().to_string().to_upper_camel_case(); + Ok(KotlinCodeOracle.convert_error_suffix(&name)) + } + + pub fn async_poll( + callable: impl Callable, + ci: &ComponentInterface, + ) -> Result<String, askama::Error> { + let ffi_func = callable.ffi_rust_future_poll(ci); + Ok(format!( + "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}" + )) + } + + pub fn async_complete( + callable: impl Callable, + ci: &ComponentInterface, + ) -> Result<String, askama::Error> { + let ffi_func = callable.ffi_rust_future_complete(ci); + let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)"); + let call = match callable.return_type() { + Some(Type::External { + kind: ExternalKind::DataClass, + name, + .. + }) => { + // Need to convert the RustBuffer from our package to the RustBuffer of the external package + let suffix = KotlinCodeOracle.class_name(ci, &name); + format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}") + } + _ => call, + }; + Ok(format!("{{ future, continuation -> {call} }}")) + } + + pub fn async_free( + callable: impl Callable, + ci: &ComponentInterface, + ) -> Result<String, askama::Error> { + let ffi_func = callable.ffi_rust_future_free(ci); + Ok(format!( + "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}" + )) + } + + /// Remove the "`" chars we put around function/variable names + /// + /// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to + /// render the name unquoted. One example is the message property for errors where we want to + /// display the name for the user. + pub fn unquote(nm: &str) -> Result<String, askama::Error> { + Ok(nm.trim_matches('`').to_string()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs new file mode 100644 index 0000000000..c39ae59cce --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs @@ -0,0 +1,27 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs new file mode 100644 index 0000000000..22495fa209 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs @@ -0,0 +1,86 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; +use crate::interface::{ComponentInterface, Radix, Type}; +use paste::paste; + +fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { + fn typed_number(type_: &Type, num_str: String) -> String { + match type_ { + // Bytes, Shorts and Ints can all be inferred from the type. + Type::Int8 | Type::Int16 | Type::Int32 => num_str, + Type::Int64 => format!("{num_str}L"), + + Type::UInt8 | Type::UInt16 | Type::UInt32 => format!("{num_str}u"), + Type::UInt64 => format!("{num_str}uL"), + + Type::Float32 => format!("{num_str}f"), + Type::Float64 => num_str, + _ => panic!("Unexpected literal: {num_str} is not a number"), + } + } + + match literal { + Literal::Boolean(v) => format!("{v}"), + Literal::String(s) => format!("\"{s}\""), + Literal::Int(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("{i:#x}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::UInt(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("{i:#x}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::Float(string, type_) => typed_number(type_, string.clone()), + + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $class_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self, _ci: &ComponentInterface) -> String { + $class_name.into() + } + + fn canonical_name(&self) -> String { + $class_name.into() + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + render_literal(&literal, ci) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "Boolean"); +impl_code_type_for_primitive!(StringCodeType, "String"); +impl_code_type_for_primitive!(BytesCodeType, "ByteArray"); +impl_code_type_for_primitive!(Int8CodeType, "Byte"); +impl_code_type_for_primitive!(Int16CodeType, "Short"); +impl_code_type_for_primitive!(Int32CodeType, "Int"); +impl_code_type_for_primitive!(Int64CodeType, "Long"); +impl_code_type_for_primitive!(UInt8CodeType, "UByte"); +impl_code_type_for_primitive!(UInt16CodeType, "UShort"); +impl_code_type_for_primitive!(UInt32CodeType, "UInt"); +impl_code_type_for_primitive!(UInt64CodeType, "ULong"); +impl_code_type_for_primitive!(Float32CodeType, "Float"); +impl_code_type_for_primitive!(Float64CodeType, "Double"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs new file mode 100644 index 0000000000..17781c2220 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/record.rs @@ -0,0 +1,27 @@ +/* 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/. */ + +use super::CodeType; +use crate::ComponentInterface; + +#[derive(Debug)] +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.rs new file mode 100644 index 0000000000..c7673882d9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/variant.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/. */ + +use super::{AsCodeType, CodeType, KotlinCodeOracle}; +use crate::interface::{ComponentInterface, Variant}; + +#[derive(Debug)] +pub(super) struct VariantCodeType { + pub v: Variant, +} + +impl CodeType for VariantCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + KotlinCodeOracle.class_name(ci, self.v.name()) + } + + fn canonical_name(&self) -> String { + self.v.name().to_string() + } +} + +impl AsCodeType for Variant { + fn as_codetype(&self) -> Box<dyn CodeType> { + Box::new(VariantCodeType { v: self.clone() }) + } +} + +impl AsCodeType for &Variant { + fn as_codetype(&self) -> Box<dyn CodeType> { + Box::new(VariantCodeType { v: (*self).clone() }) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.rs new file mode 100644 index 0000000000..466fe77879 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/mod.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/. */ + +use anyhow::Result; +use camino::{Utf8Path, Utf8PathBuf}; +use fs_err as fs; +use std::process::Command; + +pub mod gen_kotlin; +pub use gen_kotlin::{generate_bindings, Config}; +mod test; + +use super::super::interface::ComponentInterface; +pub use test::{run_script, run_test}; + +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<()> { + let mut kt_file = full_bindings_path(config, out_dir); + fs::create_dir_all(&kt_file)?; + kt_file.push(format!("{}.kt", ci.namespace())); + fs::write(&kt_file, generate_bindings(config, ci)?)?; + if try_format_code { + if let Err(e) = Command::new("ktlint").arg("-F").arg(&kt_file).output() { + println!( + "Warning: Unable to auto-format {} using ktlint: {e:?}", + kt_file.file_name().unwrap(), + ); + } + } + Ok(()) +} + +fn full_bindings_path(config: &Config, out_dir: &Utf8Path) -> Utf8PathBuf { + let package_path: Utf8PathBuf = config.package_name().split('.').collect(); + Utf8PathBuf::from(out_dir).join(package_path) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt new file mode 100644 index 0000000000..c6a32655f2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -0,0 +1,44 @@ +// Async return type handlers + +internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() + +internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>() + +// FFI type for Rust future continuations +internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { + override fun callback(continuationHandle: USize, pollResult: Short) { + uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) + } + + internal fun register(lib: _UniFFILib) { + lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this) + } +} + +internal suspend fun<T, F, E: Exception> uniffiRustCallAsync( + rustFuture: Pointer, + pollFunc: (Pointer, USize) -> Unit, + completeFunc: (Pointer, RustCallStatus) -> F, + freeFunc: (Pointer) -> Unit, + liftFunc: (F) -> T, + errorHandler: CallStatusErrorHandler<E> +): T { + try { + do { + val pollResult = suspendCancellableCoroutine<Short> { continuation -> + pollFunc( + rustFuture, + uniffiContinuationHandleMap.insert(continuation) + ) + } + } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); + + return liftFunc( + rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) + ) + } finally { + freeFunc(rustFuture) + } +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt new file mode 100644 index 0000000000..8cfa2ce000 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt @@ -0,0 +1,19 @@ +public object FfiConverterBoolean: FfiConverter<Boolean, Byte> { + override fun lift(value: Byte): Boolean { + return value.toInt() != 0 + } + + override fun read(buf: ByteBuffer): Boolean { + return lift(buf.get()) + } + + override fun lower(value: Boolean): Byte { + return if (value) 1.toByte() else 0.toByte() + } + + override fun allocationSize(value: Boolean) = 1 + + override fun write(value: Boolean, buf: ByteBuffer) { + buf.put(lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt new file mode 100644 index 0000000000..4840a199b4 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt @@ -0,0 +1,15 @@ +public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> { + override fun read(buf: ByteBuffer): ByteArray { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr + } + override fun allocationSize(value: ByteArray): Int { + return 4 + value.size + } + override fun write(value: ByteArray, buf: ByteBuffer) { + buf.putInt(value.size) + buf.put(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt new file mode 100644 index 0000000000..62a71e02f1 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -0,0 +1,78 @@ +internal typealias Handle = Long +internal class ConcurrentHandleMap<T>( + private val leftMap: MutableMap<Handle, T> = mutableMapOf(), + private val rightMap: MutableMap<T, Handle> = mutableMapOf() +) { + private val lock = java.util.concurrent.locks.ReentrantLock() + private val currentHandle = AtomicLong(0L) + private val stride = 1L + + fun insert(obj: T): Handle = + lock.withLock { + rightMap[obj] ?: + currentHandle.getAndAdd(stride) + .also { handle -> + leftMap[handle] = obj + rightMap[obj] = handle + } + } + + fun get(handle: Handle) = lock.withLock { + leftMap[handle] + } + + fun delete(handle: Handle) { + this.remove(handle) + } + + fun remove(handle: Handle): T? = + lock.withLock { + leftMap.remove(handle)?.let { obj -> + rightMap.remove(obj) + obj + } + } +} + +interface ForeignCallback : com.sun.jna.Callback { + public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +internal const val IDX_CALLBACK_FREE = 0 +// Callback return codes +internal const val UNIFFI_CALLBACK_SUCCESS = 0 +internal const val UNIFFI_CALLBACK_ERROR = 1 +internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 + +public abstract class FfiConverterCallbackInterface<CallbackInterface>( + protected val foreignCallback: ForeignCallback +): FfiConverter<CallbackInterface, Handle> { + private val handleMap = ConcurrentHandleMap<CallbackInterface>() + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal abstract fun register(lib: _UniFFILib) + + fun drop(handle: Handle): RustBuffer.ByValue { + return handleMap.remove(handle).let { RustBuffer.ByValue() } + } + + override fun lift(value: Handle): CallbackInterface { + return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + } + + override fun read(buf: ByteBuffer) = lift(buf.getLong()) + + override fun lower(value: CallbackInterface) = + handleMap.insert(value).also { + assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } + } + + override fun allocationSize(value: CallbackInterface) = 8 + + override fun write(value: CallbackInterface, buf: ByteBuffer) { + buf.putLong(lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt new file mode 100644 index 0000000000..5a29f0acc3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -0,0 +1,129 @@ +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- let type_name = cbi|type_name(ci) %} +{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %} + +{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %} +{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} +{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }} +{{- self.add_import("kotlin.concurrent.withLock") }} + +// Declaration and FfiConverters for {{ type_name }} Callback Interface + +public interface {{ type_name }} { + {% for meth in cbi.methods() -%} + fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} + {%- else -%} + {%- endmatch %} + {% endfor %} + companion object +} + +// The ForeignCallback that is passed to Rust. +internal class {{ foreign_callback }} : ForeignCallback { + @Suppress("TooGenericExceptionCaught") + override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + val cb = {{ ffi_converter_name }}.lift(handle) + return when (method) { + IDX_CALLBACK_FREE -> { + {{ ffi_converter_name }}.drop(handle) + // Successful return + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + UNIFFI_CALLBACK_SUCCESS + } + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + {{ loop.index }} -> { + // Call the method, write to outBuf and return a status code + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info + try { + this.{{ method_name }}(cb, argsData, argsLen, outBuf) + } catch (e: Throwable) { + // Unexpected error + try { + // Try to serialize the error into a string + outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString())) + } catch (e: Throwable) { + // If that fails, then it's time to give up and just return + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + {% endfor %} + else -> { + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + try { + // Try to serialize the error into a string + outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index")) + } catch (e: Throwable) { + // If that fails, then it's time to give up and just return + } + UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + } + } + + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name %} + @Suppress("UNUSED_PARAMETER") + private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { + {%- if meth.arguments().len() > 0 %} + val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + {%- endif %} + + {%- match meth.return_type() %} + {%- when Some with (return_type) %} + fun makeCall() : Int { + val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {{ arg|read_fn }}(argsBuf) + {% if !loop.last %}, {% endif %} + {%- endfor %} + ) + outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue)) + return UNIFFI_CALLBACK_SUCCESS + } + {%- when None %} + fun makeCall() : Int { + kotlinCallbackInterface.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {{ arg|read_fn }}(argsBuf) + {%- if !loop.last %}, {% endif %} + {%- endfor %} + ) + return UNIFFI_CALLBACK_SUCCESS + } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + fun makeCallAndHandleError() : Int = makeCall() + {%- when Some(error_type) %} + fun makeCallAndHandleError() : Int = try { + makeCall() + } catch (e: {{ error_type|type_name(ci) }}) { + // Expected error, serialize it into outBuf + outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)) + UNIFFI_CALLBACK_ERROR + } + {%- endmatch %} + + return makeCallAndHandleError() + } + {% endfor %} +} + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>( + foreignCallback = {{ foreign_callback }}() +) { + override fun register(lib: _UniFFILib) { + rustCall() { status -> + lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt new file mode 100644 index 0000000000..04150c5d78 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt @@ -0,0 +1,62 @@ +{%- match config.custom_types.get(name.as_str()) %} +{%- when None %} +{#- Define the type using typealiases to the builtin #} +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias {{ name }} = {{ builtin|type_name(ci) }} +public typealias {{ ffi_converter_name }} = {{ builtin|ffi_converter_name }} + +{%- when Some with (config) %} + +{%- let ffi_type_name=builtin|ffi_type|ffi_type_name_by_value %} + +{# When the config specifies a different type name, create a typealias for it #} +{%- match config.type_name %} +{%- when Some(concrete_type_name) %} +/** + * Typealias from the type name used in the UDL file to the custom type. This + * is needed because the UDL type name is used in function/method signatures. + * It's also what we have an external type that references a custom type. + */ +public typealias {{ name }} = {{ concrete_type_name }} +{%- else %} +{%- endmatch %} + +{%- match config.imports %} +{%- when Some(imports) %} +{%- for import_name in imports %} +{{ self.add_import(import_name) }} +{%- endfor %} +{%- else %} +{%- endmatch %} + +public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_name }}> { + override fun lift(value: {{ ffi_type_name }}): {{ name }} { + val builtinValue = {{ builtin|lift_fn }}(value) + return {{ config.into_custom.render("builtinValue") }} + } + + override fun lower(value: {{ name }}): {{ ffi_type_name }} { + val builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|lower_fn }}(builtinValue) + } + + override fun read(buf: ByteBuffer): {{ name }} { + val builtinValue = {{ builtin|read_fn }}(buf) + return {{ config.into_custom.render("builtinValue") }} + } + + override fun allocationSize(value: {{ name }}): Int { + val builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|allocation_size_fn }}(builtinValue) + } + + override fun write(value: {{ name }}, buf: ByteBuffer) { + val builtinValue = {{ config.from_custom.render("value") }} + {{ builtin|write_fn }}(builtinValue, buf) + } +} +{%- endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt new file mode 100644 index 0000000000..4237c6f9a8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt @@ -0,0 +1,36 @@ +public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> { + override fun read(buf: ByteBuffer): java.time.Duration { + // Type mismatch (should be u64) but we check for overflow/underflow below + val seconds = buf.getLong() + // Type mismatch (should be u32) but we check for overflow/underflow below + val nanoseconds = buf.getInt().toLong() + if (seconds < 0) { + throw java.time.DateTimeException("Duration exceeds minimum or maximum value supported by uniffi") + } + if (nanoseconds < 0) { + throw java.time.DateTimeException("Duration nanoseconds exceed minimum or maximum supported by uniffi") + } + return java.time.Duration.ofSeconds(seconds, nanoseconds) + } + + // 8 bytes for seconds, 4 bytes for nanoseconds + override fun allocationSize(value: java.time.Duration) = 12 + + override fun write(value: java.time.Duration, buf: ByteBuffer) { + if (value.seconds < 0) { + // Rust does not support negative Durations + throw IllegalArgumentException("Invalid duration, must be non-negative") + } + + if (value.nano < 0) { + // Java docs provide guarantee that nano will always be positive, so this should be impossible + // See: https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html + throw IllegalArgumentException("Invalid duration, nano value must be non-negative") + } + + // Type mismatch (should be u64) but since Rust doesn't support negative durations we should be OK + buf.putLong(value.seconds) + // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK + buf.putInt(value.nano) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt new file mode 100644 index 0000000000..d4c4a1684a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -0,0 +1,110 @@ +{# +// Kotlin's `enum class` construct doesn't support variants with associated data, +// but is a little nicer for consumers than its `sealed class` enum pattern. +// So, we switch here, using `enum class` for enums with no associated data +// and `sealed class` for the general case. +#} + +{%- if e.is_flat() %} + +enum class {{ type_name }} { + {% for variant in e.variants() -%} + {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %} + {%- endfor %} + companion object +} + +public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { + override fun read(buf: ByteBuffer) = try { + {{ type_name }}.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: {{ type_name }}) = 4 + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + +{% else %} + +sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} { + {% for variant in e.variants() -%} + {% if !variant.has_fields() -%} + object {{ variant|type_name(ci) }} : {{ type_name }}() + {% else -%} + data class {{ variant|type_name(ci) }}( + {% for field in variant.fields() -%} + val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} + {% endfor -%} + ) : {{ type_name }}() { + companion object + } + {%- endif %} + {% endfor %} + + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + when(this) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|type_name(ci) }} -> { + {%- if variant.has_fields() %} + {% call kt::destroy_fields(variant) %} + {% else -%} + // Nothing to destroy + {%- endif %} + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + {% endif %} + companion object +} + +public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}>{ + override fun read(buf: ByteBuffer): {{ type_name }} { + return when(buf.getInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|type_name(ci) }}{% if variant.has_fields() %}( + {% for field in variant.fields() -%} + {{ field|read_fn }}(buf), + {% endfor -%} + ){%- endif -%} + {%- endfor %} + else -> throw RuntimeException("invalid enum value, something is very wrong!!") + } + } + + override fun allocationSize(value: {{ type_name }}) = when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|type_name(ci) }} -> { + // Add the size for the Int that specifies the variant plus the size needed for all fields + ( + 4 + {%- for field in variant.fields() %} + + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + ) + } + {%- endfor %} + } + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|type_name(ci) }} -> { + buf.putInt({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + Unit + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } +} + +{% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt new file mode 100644 index 0000000000..986db5424d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -0,0 +1,111 @@ +{%- let type_name = type_|type_name(ci) %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} + +{% if e.is_flat() %} +sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} { + // Each variant is a nested class + // Flat enums carries a string error message, so no special implementation is necessary. + {% for variant in e.variants() -%} + class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message) + {% endfor %} + + companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) + } +} +{%- else %} +sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} { + // Each variant is a nested class + {% for variant in e.variants() -%} + {%- let variant_name = variant|error_variant_name %} + class {{ variant_name }}( + {% for field in variant.fields() -%} + val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} + {% endfor -%} + ) : {{ type_name }}() { + override val message + get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}" + } + {% endfor %} + + companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) + } + + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + when(this) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|error_variant_name }} -> { + {%- if variant.has_fields() %} + {% call kt::destroy_fields(variant) %} + {% else -%} + // Nothing to destroy + {%- endif %} + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + {% endif %} +} +{%- endif %} + +public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }}> { + override fun read(buf: ByteBuffer): {{ type_name }} { + {% if e.is_flat() %} + return when(buf.getInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({{ Type::String.borrow()|read_fn }}(buf)) + {%- endfor %} + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + {% else %} + + return when(buf.getInt()) { + {%- for variant in e.variants() %} + {{ loop.index }} -> {{ type_name }}.{{ variant|error_variant_name }}({% if variant.has_fields() %} + {% for field in variant.fields() -%} + {{ field|read_fn }}(buf), + {% endfor -%} + {%- endif -%}) + {%- endfor %} + else -> throw RuntimeException("invalid error enum value, something is very wrong!!") + } + {%- endif %} + } + + override fun allocationSize(value: {{ type_name }}): Int { + {%- if e.is_flat() %} + return 4 + {%- else %} + return when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|error_variant_name }} -> ( + // Add the size for the Int that specifies the variant plus the size needed for all fields + 4 + {%- for field in variant.fields() %} + + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + ) + {%- endfor %} + } + {%- endif %} + } + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + when(value) { + {%- for variant in e.variants() %} + is {{ type_name }}.{{ variant|error_variant_name }} -> { + buf.putInt({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + Unit + } + {%- endfor %} + }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } + } + +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt new file mode 100644 index 0000000000..0fade7a0bc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt @@ -0,0 +1,9 @@ +{%- let package_name=self.external_type_package_name(module_path, namespace) %} +{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %} +{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %} +{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %} +{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %} + +{{- self.add_import(fully_qualified_type_name) }} +{{- self.add_import(fully_qualified_ffi_converter_name) }} +{{ self.add_import_as(fully_qualified_rustbuffer_name, local_rustbuffer_name) }} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt new file mode 100644 index 0000000000..3b2c9d225a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt @@ -0,0 +1,71 @@ +// The FfiConverter interface handles converter types to and from the FFI +// +// All implementing objects should be public to support external types. When a +// type is external we need to import it's FfiConverter. +public interface FfiConverter<KotlinType, FfiType> { + // Convert an FFI type to a Kotlin type + fun lift(value: FfiType): KotlinType + + // Convert an Kotlin type to an FFI type + fun lower(value: KotlinType): FfiType + + // Read a Kotlin type from a `ByteBuffer` + fun read(buf: ByteBuffer): KotlinType + + // Calculate bytes to allocate when creating a `RustBuffer` + // + // This must return at least as many bytes as the write() function will + // write. It can return more bytes than needed, for example when writing + // Strings we can't know the exact bytes needed until we the UTF-8 + // encoding, so we pessimistically allocate the largest size possible (3 + // bytes per codepoint). Allocating extra bytes is not really a big deal + // because the `RustBuffer` is short-lived. + fun allocationSize(value: KotlinType): Int + + // Write a Kotlin type to a `ByteBuffer` + fun write(value: KotlinType, buf: ByteBuffer) + + // Lower a value into a `RustBuffer` + // + // This method lowers a value into a `RustBuffer` rather than the normal + // FfiType. It's used by the callback interface code. Callback interface + // returns are always serialized into a `RustBuffer` regardless of their + // normal FFI type. + fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { + val rbuf = RustBuffer.alloc(allocationSize(value)) + try { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { + it.order(ByteOrder.BIG_ENDIAN) + } + write(value, bbuf) + rbuf.writeField("len", bbuf.position()) + return rbuf + } catch (e: Throwable) { + RustBuffer.free(rbuf) + throw e + } + } + + // Lift a value from a `RustBuffer`. + // + // This here mostly because of the symmetry with `lowerIntoRustBuffer()`. + // It's currently only used by the `FfiConverterRustBuffer` class below. + fun liftFromRustBuffer(rbuf: RustBuffer.ByValue): KotlinType { + val byteBuf = rbuf.asByteBuffer()!! + try { + val item = read(byteBuf) + if (byteBuf.hasRemaining()) { + throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") + } + return item + } finally { + RustBuffer.free(rbuf) + } + } +} + +// FfiConverter that uses `RustBuffer` as the FfiType +public interface FfiConverterRustBuffer<KotlinType>: FfiConverter<KotlinType, RustBuffer.ByValue> { + override fun lift(value: RustBuffer.ByValue) = liftFromRustBuffer(value) + override fun lower(value: KotlinType) = lowerIntoRustBuffer(value) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt new file mode 100644 index 0000000000..eafec5d122 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterFloat: FfiConverter<Float, Float> { + override fun lift(value: Float): Float { + return value + } + + override fun read(buf: ByteBuffer): Float { + return buf.getFloat() + } + + override fun lower(value: Float): Float { + return value + } + + override fun allocationSize(value: Float) = 4 + + override fun write(value: Float, buf: ByteBuffer) { + buf.putFloat(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt new file mode 100644 index 0000000000..9fc2892c95 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterDouble: FfiConverter<Double, Double> { + override fun lift(value: Double): Double { + return value + } + + override fun read(buf: ByteBuffer): Double { + return buf.getDouble() + } + + override fun lower(value: Double): Double { + return value + } + + override fun allocationSize(value: Double) = 8 + + override fun write(value: Double, buf: ByteBuffer) { + buf.putDouble(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt new file mode 100644 index 0000000000..3544b2f9e6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt @@ -0,0 +1,83 @@ +{{ self.add_import("kotlinx.coroutines.CoroutineScope") }} +{{ self.add_import("kotlinx.coroutines.delay") }} +{{ self.add_import("kotlinx.coroutines.isActive") }} +{{ self.add_import("kotlinx.coroutines.launch") }} + +internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte() +internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte() +internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte() +internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte() +internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte() + +// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then +// invokes them. +internal interface UniFfiRustTaskCallback : com.sun.jna.Callback { + fun callback(rustTaskData: Pointer?, statusCode: Byte) +} + +internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback { + fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte { + if (rustTask == null) { + FfiConverterForeignExecutor.drop(handle) + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } else { + val coroutineScope = FfiConverterForeignExecutor.lift(handle) + if (coroutineScope.isActive) { + val job = coroutineScope.launch { + if (delayMs > 0) { + delay(delayMs.toLong()) + } + rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) + } + job.invokeOnCompletion { cause -> + if (cause != null) { + rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED) + } + } + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } else { + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED + } + } + } +} + +public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> { + internal val handleMap = UniFfiHandleMap<CoroutineScope>() + + internal fun drop(handle: USize) { + handleMap.remove(handle) + } + + internal fun register(lib: _UniFFILib) { + {%- match ci.ffi_foreign_executor_callback_set() %} + {%- when Some with (fn) %} + lib.{{ fn.name() }}(UniFfiForeignExecutorCallback) + {%- when None %} + {#- No foreign executor, we don't set anything #} + {% endmatch %} + } + + // Number of live handles, exposed so we can test the memory management + public fun handleCount() : Int { + return handleMap.size + } + + override fun allocationSize(value: CoroutineScope) = USize.size + + override fun lift(value: USize): CoroutineScope { + return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift") + } + + override fun read(buf: ByteBuffer): CoroutineScope { + return lift(USize.readFromBuffer(buf)) + } + + override fun lower(value: CoroutineScope): USize { + return handleMap.insert(value) + } + + override fun write(value: CoroutineScope, buf: ByteBuffer) { + lower(value).writeToBuffer(buf) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt new file mode 100644 index 0000000000..382a5f7413 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -0,0 +1,161 @@ +// A handful of classes and functions to support the generated data structures. +// This would be a good candidate for isolating in its own ffi-support lib. +// Error runtime. +@Structure.FieldOrder("code", "error_buf") +internal open class RustCallStatus : Structure() { + @JvmField var code: Byte = 0 + @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() + + class ByValue: RustCallStatus(), Structure.ByValue + + fun isSuccess(): Boolean { + return code == 0.toByte() + } + + fun isError(): Boolean { + return code == 1.toByte() + } + + fun isPanic(): Boolean { + return code == 2.toByte() + } +} + +class InternalException(message: String) : Exception(message) + +// Each top-level error class has a companion object that can lift the error from the call status's rust buffer +interface CallStatusErrorHandler<E> { + fun lift(error_buf: RustBuffer.ByValue): E; +} + +// Helpers for calling Rust +// In practice we usually need to be synchronized to call this safely, so it doesn't +// synchronize itself + +// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err +private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U { + var status = RustCallStatus(); + val return_value = callback(status) + checkCallStatus(errorHandler, status) + return return_value +} + +// Check RustCallStatus and throw an error if the call wasn't successful +private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) { + if (status.isSuccess()) { + return + } else if (status.isError()) { + throw errorHandler.lift(status.error_buf) + } else if (status.isPanic()) { + // when the rust code sees a panic, it tries to construct a rustbuffer + // with the message. but if that code panics, then it just sends back + // an empty buffer. + if (status.error_buf.len > 0) { + throw InternalException({{ Type::String.borrow()|lift_fn }}(status.error_buf)) + } else { + throw InternalException("Rust panic") + } + } else { + throw InternalException("Unknown rust call status: $status.code") + } +} + +// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> { + override fun lift(error_buf: RustBuffer.ByValue): InternalException { + RustBuffer.free(error_buf) + return InternalException("Unexpected CALL_ERROR") + } +} + +// Call a rust function that returns a plain value +private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U { + return rustCallWithError(NullCallStatusErrorHandler, callback); +} + +// IntegerType that matches Rust's `usize` / C's `size_t` +public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) { + // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin. + override fun toByte() = toInt().toByte() + // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed. + @Deprecated("`toInt().toChar()` is deprecated") + override fun toChar() = toInt().toChar() + override fun toShort() = toInt().toShort() + + fun writeToBuffer(buf: ByteBuffer) { + // Make sure we always write usize integers using native byte-order, since they may be + // casted to pointer values + buf.order(ByteOrder.nativeOrder()) + try { + when (Native.SIZE_T_SIZE) { + 4 -> buf.putInt(toInt()) + 8 -> buf.putLong(toLong()) + else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") + } + } finally { + buf.order(ByteOrder.BIG_ENDIAN) + } + } + + companion object { + val size: Int + get() = Native.SIZE_T_SIZE + + fun readFromBuffer(buf: ByteBuffer) : USize { + // Make sure we always read usize integers using native byte-order, since they may be + // casted from pointer values + buf.order(ByteOrder.nativeOrder()) + try { + return when (Native.SIZE_T_SIZE) { + 4 -> USize(buf.getInt().toLong()) + 8 -> USize(buf.getLong()) + else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") + } + } finally { + buf.order(ByteOrder.BIG_ENDIAN) + } + } + } +} + + +// Map handles to objects +// +// This is used when the Rust code expects an opaque pointer to represent some foreign object. +// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an +// object reference , nor does it support leaking a reference to Rust. +// +// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to +// Rust when it needs an opaque pointer. +// +// TODO: refactor callbacks to use this class +internal class UniFfiHandleMap<T: Any> { + private val map = ConcurrentHashMap<USize, T>() + // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible + // values seems like enough. If somehow we generate 4 billion handles, then this will wrap + // around back to zero and we can assume the first handle generated will have been dropped by + // then. + private val counter = java.util.concurrent.atomic.AtomicInteger(0) + + val size: Int + get() = map.size + + fun insert(obj: T): USize { + val handle = USize(counter.getAndAdd(1).toLong()) + map.put(handle, obj) + return handle + } + + fun get(handle: USize): T? { + return map.get(handle) + } + + fun remove(handle: USize): T? { + return map.remove(handle) + } +} + +// FFI type for Rust future continuations +internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { + fun callback(continuationHandle: USize, pollResult: Short); +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt new file mode 100644 index 0000000000..75564276be --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterShort: FfiConverter<Short, Short> { + override fun lift(value: Short): Short { + return value + } + + override fun read(buf: ByteBuffer): Short { + return buf.getShort() + } + + override fun lower(value: Short): Short { + return value + } + + override fun allocationSize(value: Short) = 2 + + override fun write(value: Short, buf: ByteBuffer) { + buf.putShort(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt new file mode 100644 index 0000000000..b7a8131c8b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterInt: FfiConverter<Int, Int> { + override fun lift(value: Int): Int { + return value + } + + override fun read(buf: ByteBuffer): Int { + return buf.getInt() + } + + override fun lower(value: Int): Int { + return value + } + + override fun allocationSize(value: Int) = 4 + + override fun write(value: Int, buf: ByteBuffer) { + buf.putInt(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt new file mode 100644 index 0000000000..601cfc7c2c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterLong: FfiConverter<Long, Long> { + override fun lift(value: Long): Long { + return value + } + + override fun read(buf: ByteBuffer): Long { + return buf.getLong() + } + + override fun lower(value: Long): Long { + return value + } + + override fun allocationSize(value: Long) = 8 + + override fun write(value: Long, buf: ByteBuffer) { + buf.putLong(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt new file mode 100644 index 0000000000..9237768dbf --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterByte: FfiConverter<Byte, Byte> { + override fun lift(value: Byte): Byte { + return value + } + + override fun read(buf: ByteBuffer): Byte { + return buf.get() + } + + override fun lower(value: Byte): Byte { + return value + } + + override fun allocationSize(value: Byte) = 1 + + override fun write(value: Byte, buf: ByteBuffer) { + buf.put(value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt new file mode 100644 index 0000000000..776c402727 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt @@ -0,0 +1,34 @@ +{%- let key_type_name = key_type|type_name(ci) %} +{%- let value_type_name = value_type|type_name(ci) %} +public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_name }}, {{ value_type_name }}>> { + override fun read(buf: ByteBuffer): Map<{{ key_type_name }}, {{ value_type_name }}> { + val len = buf.getInt() + return buildMap<{{ key_type_name }}, {{ value_type_name }}>(len) { + repeat(len) { + val k = {{ key_type|read_fn }}(buf) + val v = {{ value_type|read_fn }}(buf) + this[k] = v + } + } + } + + override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int { + val spaceForMapSize = 4 + val spaceForChildren = value.map { (k, v) -> + {{ key_type|allocation_size_fn }}(k) + + {{ value_type|allocation_size_fn }}(v) + }.sum() + return spaceForMapSize + spaceForChildren + } + + override fun write(value: Map<{{ key_type_name }}, {{ value_type_name }}>, buf: ByteBuffer) { + buf.putInt(value.size) + // The parens on `(k, v)` here ensure we're calling the right method, + // which is important for compatibility with older android devices. + // Ref https://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/ + value.forEach { (k, v) -> + {{ key_type|write_fn }}(k, buf) + {{ value_type|write_fn }}(v, buf) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt new file mode 100644 index 0000000000..6a3aeada35 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -0,0 +1,57 @@ +@Synchronized +private fun findLibraryName(componentName: String): String { + val libOverride = System.getProperty("uniffi.component.$componentName.libraryOverride") + if (libOverride != null) { + return libOverride + } + return "{{ config.cdylib_name() }}" +} + +private inline fun <reified Lib : Library> loadIndirect( + componentName: String +): Lib { + return Native.load<Lib>(findLibraryName(componentName), Lib::class.java) +} + +// A JNA Library to expose the extern-C FFI definitions. +// This is an implementation detail which will be called internally by the public API. + +internal interface _UniFFILib : Library { + companion object { + internal val INSTANCE: _UniFFILib by lazy { + loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}") + .also { lib: _UniFFILib -> + uniffiCheckContractApiVersion(lib) + uniffiCheckApiChecksums(lib) + {% for fn in self.initialization_fns() -%} + {{ fn }}(lib) + {% endfor -%} + } + } + } + + {% for func in ci.iter_ffi_function_definitions() -%} + fun {{ func.name() }}( + {%- call kt::arg_list_ffi_decl(func) %} + ): {% match func.return_type() %}{% when Some with (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value }}{% when None %}Unit{% endmatch %} + {% endfor %} +} + +private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { + // Get the bindings contract version from our ComponentInterface + val bindings_contract_version = {{ ci.uniffi_contract_version() }} + // Get the scaffolding contract version by calling the into the dylib + val scaffolding_contract_version = lib.{{ ci.ffi_uniffi_contract_version().name() }}() + if (bindings_contract_version != scaffolding_contract_version) { + throw RuntimeException("UniFFI contract version mismatch: try cleaning and rebuilding your project") + } +} + +@Suppress("UNUSED_PARAMETER") +private fun uniffiCheckApiChecksums(lib: _UniFFILib) { + {%- for (name, expected_checksum) in ci.iter_checksums() %} + if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + {%- endfor %} +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt new file mode 100644 index 0000000000..b9352c690f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt @@ -0,0 +1,161 @@ +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance<Disposable>() + .forEach(Disposable::destroy) + } + } +} + +inline fun <T : Disposable?, R> T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +// The base class for all UniFFI Object types. +// +// This class provides core operations for working with the Rust `Arc<T>` pointer to +// the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an `FFIObject` instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so will +// leak the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// In the future we may be able to replace some of this with automatic finalization logic, such as using +// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is +// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also +// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], +// so there would still be some complexity here). +// +// Sigh...all of this for want of a robust finalization mechanism. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// +abstract class FFIObject( + protected val pointer: Pointer +): Disposable, AutoCloseable { + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + open protected fun freeRustArcPtr() { + // To be overridden in subclasses. + } + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.pointer) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + this.freeRustArcPtr() + } + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt new file mode 100644 index 0000000000..8ce27a5d04 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -0,0 +1,138 @@ +{%- let obj = ci|get_object_definition(name) %} +{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %} +{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} +{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }} + +public interface {{ type_name }}Interface { + {% for meth in obj.methods() -%} + {%- match meth.throws_type() -%} + {%- when Some with (throwable) -%} + @Throws({{ throwable|type_name(ci) }}::class) + {%- when None -%} + {%- endmatch %} + {% if meth.is_async() -%} + suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) + {%- else -%} + fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) + {%- endif %} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} + {%- when None -%} + {%- endmatch -%} + + {% endfor %} + companion object +} + +class {{ type_name }}( + pointer: Pointer +) : FFIObject(pointer), {{ type_name }}Interface { + + {%- match obj.primary_constructor() %} + {%- when Some with (cons) %} + constructor({% call kt::arg_list_decl(cons) -%}) : + this({% call kt::to_ffi_call(cons) %}) + {%- when None %} + {%- endmatch %} + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status) + } + } + + {% for meth in obj.methods() -%} + {%- match meth.throws_type() -%} + {%- when Some with (throwable) %} + @Throws({{ throwable|type_name(ci) }}::class) + {%- else -%} + {%- endmatch -%} + {%- if meth.is_async() %} + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}( + thisPtr, + {% call kt::arg_list_lowered(meth) %} + ) + }, + {{ meth|async_poll(ci) }}, + {{ meth|async_complete(ci) }}, + {{ meth|async_free(ci) }}, + // lift function + {%- match meth.return_type() %} + {%- when Some(return_type) %} + { {{ return_type|lift_fn }}(it) }, + {%- when None %} + { Unit }, + {% endmatch %} + // Error FFI converter + {%- match meth.throws_type() %} + {%- when Some(e) %} + {{ e|type_name(ci) }}.ErrorHandler, + {%- when None %} + NullCallStatusErrorHandler, + {%- endmatch %} + ) + } + {%- else -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) -%} + override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} = + callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", meth) %} + }.let { + {{ return_type|lift_fn }}(it) + } + + {%- when None -%} + override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) = + callWithPointer { + {%- call kt::to_ffi_call_with_prefix("it", meth) %} + } + {% endmatch %} + {% endif %} + {% endfor %} + + {% if !obj.alternate_constructors().is_empty() -%} + companion object { + {% for cons in obj.alternate_constructors() -%} + fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} = + {{ type_name }}({% call kt::to_ffi_call(cons) %}) + {% endfor %} + } + {% else %} + companion object + {% endif %} +} + +public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { + override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): {{ type_name }} { + return {{ type_name }}(value) + } + + override fun read(buf: ByteBuffer): {{ type_name }} { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: {{ type_name }}) = 8 + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt new file mode 100644 index 0000000000..56cb5f87a5 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt @@ -0,0 +1,27 @@ +{%- let inner_type_name = inner_type|type_name(ci) %} + +public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_name }}?> { + override fun read(buf: ByteBuffer): {{ inner_type_name }}? { + if (buf.get().toInt() == 0) { + return null + } + return {{ inner_type|read_fn }}(buf) + } + + override fun allocationSize(value: {{ inner_type_name }}?): Int { + if (value == null) { + return 1 + } else { + return 1 + {{ inner_type|allocation_size_fn }}(value) + } + } + + override fun write(value: {{ inner_type_name }}?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + {{ inner_type|write_fn }}(value, buf) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt new file mode 100644 index 0000000000..b588ca1398 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -0,0 +1,42 @@ +{%- let rec = ci|get_record_definition(name) %} + +data class {{ type_name }} ( + {%- for field in rec.fields() %} + var {{ field.name()|var_name }}: {{ field|type_name(ci) -}} + {%- match field.default_value() %} + {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }} + {%- else %} + {%- endmatch -%} + {% if !loop.last %}, {% endif %} + {%- endfor %} +) {% if contains_object_references %}: Disposable {% endif %}{ + {% if contains_object_references %} + @Suppress("UNNECESSARY_SAFE_CALL") // codegen is much simpler if we unconditionally emit safe calls here + override fun destroy() { + {% call kt::destroy_fields(rec) %} + } + {% endif %} + companion object +} + +public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { + override fun read(buf: ByteBuffer): {{ type_name }} { + return {{ type_name }}( + {%- for field in rec.fields() %} + {{ field|read_fn }}(buf), + {%- endfor %} + ) + } + + override fun allocationSize(value: {{ type_name }}) = ( + {%- for field in rec.fields() %} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%} + {%- endfor %} + ) + + override fun write(value: {{ type_name }}, buf: ByteBuffer) { + {%- for field in rec.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt new file mode 100644 index 0000000000..dfbea24074 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt @@ -0,0 +1,87 @@ +// This is a helper for safely working with byte buffers returned from the Rust code. +// A rust-owned buffer is represented by its capacity, its current length, and a +// pointer to the underlying data. + +@Structure.FieldOrder("capacity", "len", "data") +open class RustBuffer : Structure() { + @JvmField var capacity: Int = 0 + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue: RustBuffer(), Structure.ByValue + class ByReference: RustBuffer(), Structure.ByReference + + companion object { + internal fun alloc(size: Int = 0) = rustCall() { status -> + _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status) + }.also { + if(it.data == null) { + throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") + } + } + + internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue { + var buf = RustBuffer.ByValue() + buf.capacity = capacity + buf.len = len + buf.data = data + return buf + } + + internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> + _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status) + } + } + + @Suppress("TooGenericExceptionThrown") + fun asByteBuffer() = + this.data?.getByteBuffer(0, this.len.toLong())?.also { + it.order(ByteOrder.BIG_ENDIAN) + } +} + +/** + * The equivalent of the `*mut RustBuffer` type. + * Required for callbacks taking in an out pointer. + * + * Size is the sum of all values in the struct. + */ +class RustBufferByReference : ByReference(16) { + /** + * Set the pointed-to `RustBuffer` to the given value. + */ + fun setValue(value: RustBuffer.ByValue) { + // NOTE: The offsets are as they are in the C-like struct. + val pointer = getPointer() + pointer.setInt(0, value.capacity) + pointer.setInt(4, value.len) + pointer.setPointer(8, value.data) + } + + /** + * Get a `RustBuffer.ByValue` from this reference. + */ + fun getValue(): RustBuffer.ByValue { + val pointer = getPointer() + val value = RustBuffer.ByValue() + value.writeField("capacity", pointer.getInt(0)) + value.writeField("len", pointer.getInt(4)) + value.writeField("data", pointer.getPointer(8)) + + return value + } +} + +// This is a helper for safely passing byte references into the rust code. +// It's not actually used at the moment, because there aren't many things that you +// can take a direct pointer to in the JVM, and if we're going to copy something +// then we might as well copy it into a `RustBuffer`. But it's here for API +// completeness. + +@Structure.FieldOrder("len", "data") +open class ForeignBytes : Structure() { + @JvmField var len: Int = 0 + @JvmField var data: Pointer? = null + + class ByValue : ForeignBytes(), Structure.ByValue +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt new file mode 100644 index 0000000000..876d1bc05e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt @@ -0,0 +1,23 @@ +{%- let inner_type_name = inner_type|type_name(ci) %} + +public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_type_name }}>> { + override fun read(buf: ByteBuffer): List<{{ inner_type_name }}> { + val len = buf.getInt() + return List<{{ inner_type_name }}>(len) { + {{ inner_type|read_fn }}(buf) + } + } + + override fun allocationSize(value: List<{{ inner_type_name }}>): Int { + val sizeForLength = 4 + val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum() + return sizeForLength + sizeForItems + } + + override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) { + buf.putInt(value.size) + value.forEach { + {{ inner_type|write_fn }}(it, buf) + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt new file mode 100644 index 0000000000..68324be4f9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt @@ -0,0 +1,53 @@ +public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { + // Note: we don't inherit from FfiConverterRustBuffer, because we use a + // special encoding when lowering/lifting. We can use `RustBuffer.len` to + // store our length and avoid writing it out to the buffer. + override fun lift(value: RustBuffer.ByValue): String { + try { + val byteArr = ByteArray(value.len) + value.asByteBuffer()!!.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } finally { + RustBuffer.free(value) + } + } + + override fun read(buf: ByteBuffer): String { + val len = buf.getInt() + val byteArr = ByteArray(len) + buf.get(byteArr) + return byteArr.toString(Charsets.UTF_8) + } + + fun toUtf8(value: String): ByteBuffer { + // Make sure we don't have invalid UTF-16, check for lone surrogates. + return Charsets.UTF_8.newEncoder().run { + onMalformedInput(CodingErrorAction.REPORT) + encode(CharBuffer.wrap(value)) + } + } + + override fun lower(value: String): RustBuffer.ByValue { + val byteBuf = toUtf8(value) + // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us + // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. + val rbuf = RustBuffer.alloc(byteBuf.limit()) + rbuf.asByteBuffer()!!.put(byteBuf) + return rbuf + } + + // We aren't sure exactly how many bytes our string will be once it's UTF-8 + // encoded. Allocate 3 bytes per UTF-16 code unit which will always be + // enough. + override fun allocationSize(value: String): Int { + val sizeForLength = 4 + val sizeForString = value.length * 3 + return sizeForLength + sizeForString + } + + override fun write(value: String, buf: ByteBuffer) { + val byteBuf = toUtf8(value) + buf.putInt(byteBuf.limit()) + buf.put(byteBuf) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt new file mode 100644 index 0000000000..21069d7ce8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt @@ -0,0 +1,38 @@ +public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> { + override fun read(buf: ByteBuffer): java.time.Instant { + val seconds = buf.getLong() + // Type mismatch (should be u32) but we check for overflow/underflow below + val nanoseconds = buf.getInt().toLong() + if (nanoseconds < 0) { + throw java.time.DateTimeException("Instant nanoseconds exceed minimum or maximum supported by uniffi") + } + if (seconds >= 0) { + return java.time.Instant.EPOCH.plus(java.time.Duration.ofSeconds(seconds, nanoseconds)) + } else { + return java.time.Instant.EPOCH.minus(java.time.Duration.ofSeconds(-seconds, nanoseconds)) + } + } + + // 8 bytes for seconds, 4 bytes for nanoseconds + override fun allocationSize(value: java.time.Instant) = 12 + + override fun write(value: java.time.Instant, buf: ByteBuffer) { + var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value) + + var sign = 1 + if (epochOffset.isNegative()) { + sign = -1 + epochOffset = epochOffset.negated() + } + + if (epochOffset.nano < 0) { + // Java docs provide guarantee that nano will always be positive, so this should be impossible + // See: https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html + throw IllegalArgumentException("Invalid timestamp, nano value must be non-negative") + } + + buf.putLong(sign * epochOffset.seconds) + // Type mismatch (should be u32) but since values will always be between 0 and 999,999,999 it should be OK + buf.putInt(epochOffset.nano) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt new file mode 100644 index 0000000000..6a841d3484 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt @@ -0,0 +1,51 @@ +{%- if func.is_async() %} +{%- match func.throws_type() -%} +{%- when Some with (throwable) %} +@Throws({{ throwable|type_name(ci) }}::class) +{%- else -%} +{%- endmatch %} + +@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") +suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { + return uniffiRustCallAsync( + _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}), + {{ func|async_poll(ci) }}, + {{ func|async_complete(ci) }}, + {{ func|async_free(ci) }}, + // lift function + {%- match func.return_type() %} + {%- when Some(return_type) %} + { {{ return_type|lift_fn }}(it) }, + {%- when None %} + { Unit }, + {% endmatch %} + // Error FFI converter + {%- match func.throws_type() %} + {%- when Some(e) %} + {{ e|type_name(ci) }}.ErrorHandler, + {%- when None %} + NullCallStatusErrorHandler, + {%- endmatch %} + ) +} + +{%- else %} +{%- match func.throws_type() -%} +{%- when Some with (throwable) %} +@Throws({{ throwable|type_name(ci) }}::class) +{%- else -%} +{%- endmatch -%} + +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} { + return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %}) +} +{% when None %} + +fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) = + {% call kt::to_ffi_call(func) %} + +{% endmatch %} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt new file mode 100644 index 0000000000..103d444ea3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt @@ -0,0 +1,109 @@ +{%- import "macros.kt" as kt %} + +{%- for type_ in ci.iter_types() %} +{%- let type_name = type_|type_name(ci) %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} +{%- let contains_object_references = ci.item_contains_object_references(type_) %} + +{# + # Map `Type` instances to an include statement for that type. + # + # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the + # Rust code. + # + # - When adding additional types here, make sure to also add a match arm to that function. + # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + #} +{%- match type_ %} + +{%- when Type::Boolean %} +{%- include "BooleanHelper.kt" %} + +{%- when Type::Int8 %} +{%- include "Int8Helper.kt" %} + +{%- when Type::Int16 %} +{%- include "Int16Helper.kt" %} + +{%- when Type::Int32 %} +{%- include "Int32Helper.kt" %} + +{%- when Type::Int64 %} +{%- include "Int64Helper.kt" %} + +{%- when Type::UInt8 %} +{%- include "UInt8Helper.kt" %} + +{%- when Type::UInt16 %} +{%- include "UInt16Helper.kt" %} + +{%- when Type::UInt32 %} +{%- include "UInt32Helper.kt" %} + +{%- when Type::UInt64 %} +{%- include "UInt64Helper.kt" %} + +{%- when Type::Float32 %} +{%- include "Float32Helper.kt" %} + +{%- when Type::Float64 %} +{%- include "Float64Helper.kt" %} + +{%- when Type::String %} +{%- include "StringHelper.kt" %} + +{%- when Type::Bytes %} +{%- include "ByteArrayHelper.kt" %} + +{%- when Type::Enum { name, module_path } %} +{%- let e = ci.get_enum_definition(name).unwrap() %} +{%- if !ci.is_name_used_as_error(name) %} +{% include "EnumTemplate.kt" %} +{%- else %} +{% include "ErrorTemplate.kt" %} +{%- endif -%} + +{%- when Type::Object { module_path, name, imp } %} +{% include "ObjectTemplate.kt" %} + +{%- when Type::Record { name, module_path } %} +{% include "RecordTemplate.kt" %} + +{%- when Type::Optional { inner_type } %} +{% include "OptionalTemplate.kt" %} + +{%- when Type::Sequence { inner_type } %} +{% include "SequenceTemplate.kt" %} + +{%- when Type::Map { key_type, value_type } %} +{% include "MapTemplate.kt" %} + +{%- when Type::CallbackInterface { module_path, name } %} +{% include "CallbackInterfaceTemplate.kt" %} + +{%- when Type::ForeignExecutor %} +{% include "ForeignExecutorTemplate.kt" %} + +{%- when Type::Timestamp %} +{% include "TimestampHelper.kt" %} + +{%- when Type::Duration %} +{% include "DurationHelper.kt" %} + +{%- when Type::Custom { module_path, name, builtin } %} +{% include "CustomTypeTemplate.kt" %} + +{%- when Type::External { module_path, name, namespace, kind, tagged } %} +{% include "ExternalTypeTemplate.kt" %} + +{%- else %} +{%- endmatch %} +{%- endfor %} + +{%- if ci.has_async_fns() %} +{# Import types needed for async support #} +{{ self.add_import("kotlin.coroutines.resume") }} +{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }} +{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt new file mode 100644 index 0000000000..279a8fa91b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterUShort: FfiConverter<UShort, Short> { + override fun lift(value: Short): UShort { + return value.toUShort() + } + + override fun read(buf: ByteBuffer): UShort { + return lift(buf.getShort()) + } + + override fun lower(value: UShort): Short { + return value.toShort() + } + + override fun allocationSize(value: UShort) = 2 + + override fun write(value: UShort, buf: ByteBuffer) { + buf.putShort(value.toShort()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt new file mode 100644 index 0000000000..da7b5b28d6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterUInt: FfiConverter<UInt, Int> { + override fun lift(value: Int): UInt { + return value.toUInt() + } + + override fun read(buf: ByteBuffer): UInt { + return lift(buf.getInt()) + } + + override fun lower(value: UInt): Int { + return value.toInt() + } + + override fun allocationSize(value: UInt) = 4 + + override fun write(value: UInt, buf: ByteBuffer) { + buf.putInt(value.toInt()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt new file mode 100644 index 0000000000..44d27ad36b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterULong: FfiConverter<ULong, Long> { + override fun lift(value: Long): ULong { + return value.toULong() + } + + override fun read(buf: ByteBuffer): ULong { + return lift(buf.getLong()) + } + + override fun lower(value: ULong): Long { + return value.toLong() + } + + override fun allocationSize(value: ULong) = 8 + + override fun write(value: ULong, buf: ByteBuffer) { + buf.putLong(value.toLong()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt new file mode 100644 index 0000000000..b6d176603e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt @@ -0,0 +1,19 @@ +public object FfiConverterUByte: FfiConverter<UByte, Byte> { + override fun lift(value: Byte): UByte { + return value.toUByte() + } + + override fun read(buf: ByteBuffer): UByte { + return lift(buf.get()) + } + + override fun lower(value: UByte): Byte { + return value.toByte() + } + + override fun allocationSize(value: UByte) = 1 + + override fun write(value: UByte, buf: ByteBuffer) { + buf.put(value.toByte()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt new file mode 100644 index 0000000000..6a95d6a66d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt @@ -0,0 +1,77 @@ +{# +// Template to call into rust. Used in several places. +// Variable names in `arg_list_decl` should match up with arg lists +// passed to rust via `arg_list_lowered` +#} + +{%- macro to_ffi_call(func) -%} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|type_name(ci) }}) + {%- else %} + rustCall() + {%- endmatch %} { _status -> + _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status) +} +{%- endmacro -%} + +{%- macro to_ffi_call_with_prefix(prefix, func) %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|type_name(ci) }}) + {%- else %} + rustCall() + {%- endmatch %} { _status -> + _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}( + {{- prefix }}, + {% call arg_list_lowered(func) %} + _status) +} +{%- endmacro %} + +{%- macro arg_list_lowered(func) %} + {%- for arg in func.arguments() %} + {{- arg|lower_fn }}({{ arg.name()|var_name }}), + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in kotlin declarations of methods, functions and constructors. +// Note the var_name and type_name filters. +-#} + +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} + {%- match arg.default_value() %} + {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} + {%- else %} + {%- endmatch %} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + +{% macro arg_list_protocol(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} +{#- +// Arglist as used in the _UniFFILib function declarations. +// Note unfiltered name but ffi_type_name filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + {{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}}, + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %} +{%- endmacro -%} + +// Macro for destroying fields +{%- macro destroy_fields(member) %} + Disposable.destroy( + {%- for field in member.fields() %} + this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%} + {% endfor -%}) +{%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt new file mode 100644 index 0000000000..9ee4229018 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt @@ -0,0 +1,57 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +@file:Suppress("NAME_SHADOWING") + +package {{ config.package_name() }}; + +// Common helper code. +// +// Ideally this would live in a separate .kt file where it can be unittested etc +// in isolation, and perhaps even published as a re-useable package. +// +// However, it's important that the details of how this helper code works (e.g. the +// way that different builtin types are passed across the FFI) exactly match what's +// expected by the Rust code on the other side of the interface. In practice right +// now that means coming from the exact some version of `uniffi` that was used to +// compile the Rust component. The easiest way to ensure this is to bundle the Kotlin +// helpers directly inline like we're doing here. + +import com.sun.jna.Library +import com.sun.jna.IntegerType +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure +import com.sun.jna.Callback +import com.sun.jna.ptr.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.CharBuffer +import java.nio.charset.CodingErrorAction +import java.util.concurrent.ConcurrentHashMap + +{%- for req in self.imports() %} +{{ req.render() }} +{%- endfor %} + +{% include "RustBufferTemplate.kt" %} +{% include "FfiConverterTemplate.kt" %} +{% include "Helpers.kt" %} + +// Contains loading, initialization code, +// and the FFI Function declarations in a com.sun.jna.Library. +{% include "NamespaceLibraryTemplate.kt" %} + +// Async support +{%- if ci.has_async_fns() %} +{% include "Async.kt" %} +{%- endif %} + +// Public interface members begin here. +{{ type_helper_code }} + +{%- for func in ci.function_definitions() %} +{%- include "TopLevelFunctionTemplate.kt" %} +{%- endfor %} + +{% import "macros.kt" as kt %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs new file mode 100644 index 0000000000..7b78540741 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs @@ -0,0 +1,131 @@ +/* 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/. */ + +use crate::{ + bindings::{RunScriptOptions, TargetLanguage}, + library_mode::generate_bindings, +}; +use anyhow::{bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use std::env; +use std::process::Command; +use uniffi_testing::UniFFITestHelper; + +/// Run Kotlin tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + run_script( + tmp_dir, + fixture_name, + script_file, + vec![], + &RunScriptOptions::default(), + ) +} + +/// Run a Kotlin script +/// +/// This function will set things up so that the script can import the UniFFI bindings for a crate +pub fn run_script( + tmp_dir: &str, + crate_name: &str, + script_file: &str, + args: Vec<String>, + options: &RunScriptOptions, +) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file); + let test_helper = UniFFITestHelper::new(crate_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + generate_bindings( + &cdylib_path, + None, + &[TargetLanguage::Kotlin], + &out_dir, + false, + )?; + let jar_file = build_jar(crate_name, &out_dir, options)?; + + let mut command = kotlinc_command(options); + command + .arg("-classpath") + .arg(calc_classpath(vec![&out_dir, &jar_file])) + // Enable runtime assertions, for easy testing etc. + .arg("-J-ea") + // Our test scripts should not produce any warnings. + .arg("-Werror") + .arg("-script") + .arg(script_path) + .args(if args.is_empty() { + vec![] + } else { + std::iter::once(String::from("--")).chain(args).collect() + }); + + let status = command + .spawn() + .context("Failed to spawn `kotlinc` to run Kotlin script")? + .wait() + .context("Failed to wait for `kotlinc` when running Kotlin script")?; + if !status.success() { + anyhow::bail!("running `kotlinc` failed") + } + Ok(()) +} + +/// Generate kotlin bindings for the given namespace, then use the kotlin +/// command-line tools to compile them into a .jar file. +fn build_jar( + crate_name: &str, + out_dir: &Utf8Path, + options: &RunScriptOptions, +) -> Result<Utf8PathBuf> { + let mut jar_file = Utf8PathBuf::from(out_dir); + jar_file.push(format!("{crate_name}.jar")); + let sources = glob::glob(out_dir.join("**/*.kt").as_str())? + .flatten() + .map(|p| String::from(p.to_string_lossy())) + .collect::<Vec<String>>(); + if sources.is_empty() { + bail!("No kotlin sources found in {out_dir}") + } + + let mut command = kotlinc_command(options); + command + // Our generated bindings should not produce any warnings; fail tests if they do. + .arg("-Werror") + .arg("-d") + .arg(&jar_file) + .arg("-classpath") + .arg(calc_classpath(vec![])) + .args(sources); + + let status = command + .spawn() + .context("Failed to spawn `kotlinc` to compile the bindings")? + .wait() + .context("Failed to wait for `kotlinc` when compiling the bindings")?; + if !status.success() { + bail!("running `kotlinc` failed") + } + Ok(jar_file) +} + +fn kotlinc_command(options: &RunScriptOptions) -> Command { + let mut command = Command::new("kotlinc"); + if !options.show_compiler_messages { + command.arg("-nowarn"); + } + command +} + +fn calc_classpath(extra_paths: Vec<&Utf8Path>) -> String { + extra_paths + .into_iter() + .map(|p| p.to_string()) + // Add the system classpath as a component, using the fact that env::var returns an Option, + // which implement Iterator + .chain(env::var("CLASSPATH")) + .collect::<Vec<String>>() + .join(":") +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/mod.rs new file mode 100644 index 0000000000..d39202bcf2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/mod.rs @@ -0,0 +1,125 @@ +/* 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/. */ + +//! Generate foreign language bindings for a uniffi component. +//! +//! This module contains all the code for generating foreign language bindings, +//! along with some helpers for executing foreign language scripts or tests. + +use anyhow::{bail, Result}; +use camino::Utf8Path; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use crate::interface::ComponentInterface; + +pub mod kotlin; +pub mod python; +pub mod ruby; +pub mod swift; + +/// Enumeration of all foreign language targets currently supported by this crate. +/// +/// The functions in this module will delegate to a language-specific backend based +/// on the provided `TargetLanguage`. For convenience of calling code we also provide +/// a few `TryFrom` implementations to help guess the correct target language from +/// e.g. a file extension of command-line argument. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] +pub enum TargetLanguage { + Kotlin, + Swift, + Python, + Ruby, +} + +impl fmt::Display for TargetLanguage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Kotlin => write!(f, "kotlin"), + Self::Swift => write!(f, "swift"), + Self::Python => write!(f, "python"), + Self::Ruby => write!(f, "ruby"), + } + } +} + +/// Mode for the `run_script` function defined for each language +#[derive(Clone, Debug)] +pub struct RunScriptOptions { + pub show_compiler_messages: bool, +} + +impl Default for RunScriptOptions { + fn default() -> Self { + Self { + show_compiler_messages: true, + } + } +} + +impl TryFrom<&str> for TargetLanguage { + type Error = anyhow::Error; + fn try_from(value: &str) -> Result<Self> { + Ok(match value.to_ascii_lowercase().as_str() { + "kotlin" | "kt" | "kts" => TargetLanguage::Kotlin, + "swift" => TargetLanguage::Swift, + "python" | "py" => TargetLanguage::Python, + "ruby" | "rb" => TargetLanguage::Ruby, + _ => bail!("Unknown or unsupported target language: \"{value}\""), + }) + } +} + +impl TryFrom<&std::ffi::OsStr> for TargetLanguage { + type Error = anyhow::Error; + fn try_from(value: &std::ffi::OsStr) -> Result<Self> { + match value.to_str() { + None => bail!("Unreadable target language"), + Some(s) => s.try_into(), + } + } +} + +impl TryFrom<String> for TargetLanguage { + type Error = anyhow::Error; + fn try_from(value: String) -> Result<Self> { + TryFrom::try_from(value.as_str()) + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + #[serde(default)] + pub(crate) kotlin: kotlin::Config, + #[serde(default)] + pub(crate) swift: swift::Config, + #[serde(default)] + pub(crate) python: python::Config, + #[serde(default)] + pub(crate) ruby: ruby::Config, +} + +/// Generate foreign language bindings from a compiled `uniffi` library. +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + language: TargetLanguage, + try_format_code: bool, +) -> Result<()> { + match language { + TargetLanguage::Kotlin => { + kotlin::write_bindings(&config.kotlin, ci, out_dir, try_format_code)? + } + TargetLanguage::Swift => { + swift::write_bindings(&config.swift, ci, out_dir, try_format_code)? + } + TargetLanguage::Python => { + python::write_bindings(&config.python, ci, out_dir, try_format_code)? + } + TargetLanguage::Ruby => ruby::write_bindings(&config.ruby, ci, out_dir, try_format_code)?, + } + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs new file mode 100644 index 0000000000..9c93965e35 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; + +#[derive(Debug)] +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self) -> String { + super::PythonCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("CallbackInterface{}", self.id) + } + + fn literal(&self, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs new file mode 100644 index 0000000000..b91bcbe18f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs @@ -0,0 +1,108 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::{Literal, Type}; + +#[derive(Debug)] +pub struct OptionalCodeType { + inner: Type, +} + +impl OptionalCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } +} + +impl CodeType for OptionalCodeType { + fn type_label(&self) -> String { + format!( + "typing.Optional[{}]", + super::PythonCodeOracle.find(&self.inner).type_label() + ) + } + + fn canonical_name(&self) -> String { + format!( + "Optional{}", + super::PythonCodeOracle.find(&self.inner).canonical_name(), + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::Null => "None".into(), + _ => super::PythonCodeOracle.find(&self.inner).literal(literal), + } + } +} + +#[derive(Debug)] +pub struct SequenceCodeType { + inner: Type, +} + +impl SequenceCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } +} + +impl CodeType for SequenceCodeType { + fn type_label(&self) -> String { + // Python 3.8 and below do not support `list[T]` + format!( + "typing.List[{}]", + super::PythonCodeOracle.find(&self.inner).type_label() + ) + } + + fn canonical_name(&self) -> String { + format!( + "Sequence{}", + super::PythonCodeOracle.find(&self.inner).canonical_name(), + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::EmptySequence => "[]".into(), + _ => unimplemented!(), + } + } +} + +#[derive(Debug)] +pub struct MapCodeType { + key: Type, + value: Type, +} + +impl MapCodeType { + pub fn new(key: Type, value: Type) -> Self { + Self { key, value } + } +} + +impl CodeType for MapCodeType { + fn type_label(&self) -> String { + "dict".to_string() + } + + fn canonical_name(&self) -> String { + format!( + "Map{}{}", + super::PythonCodeOracle.find(&self.key).canonical_name(), + super::PythonCodeOracle.find(&self.value).canonical_name(), + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::EmptyMap => "{}".into(), + _ => unimplemented!(), + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs new file mode 100644 index 0000000000..f735899f3d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + Self { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs new file mode 100644 index 0000000000..83ce177e07 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs @@ -0,0 +1,39 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; + +#[derive(Debug)] +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self) -> String { + super::PythonCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, literal: &Literal) -> String { + if let Literal::Enum(v, _) = literal { + format!( + "{}.{}", + self.type_label(), + super::PythonCodeOracle.enum_variant_name(v) + ) + } else { + unreachable!(); + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs new file mode 100644 index 0000000000..aa1c0db75e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +use crate::backend::{ Literal}; +use super::CodeType; + +#[derive(Debug)] +pub struct ErrorCodeType { + id: String, +} + +impl ErrorCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ErrorCodeType { + fn type_label(&self) -> String { + super::PythonCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs new file mode 100644 index 0000000000..be3ba1d791 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs @@ -0,0 +1,18 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ForeignExecutorCodeType; + +impl CodeType for ForeignExecutorCodeType { + fn type_label(&self) -> String { + "asyncio.BaseEventLoop".into() + } + + fn canonical_name(&self) -> String { + "ForeignExecutor".into() + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs new file mode 100644 index 0000000000..0d19c4bb3c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + Self { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs new file mode 100644 index 0000000000..07ff5cd0d7 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs @@ -0,0 +1,34 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; +use paste::paste; + +macro_rules! impl_code_type_for_miscellany { + ($T:ty, $canonical_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self) -> String { + format!("{}", $canonical_name) + } + + fn canonical_name(&self) -> String { + format!("{}", $canonical_name) + } + + fn literal(&self, _literal: &Literal) -> String { + unreachable!() + } + } + } + }; +} + +impl_code_type_for_miscellany!(TimestampCodeType, "Timestamp"); + +impl_code_type_for_miscellany!(DurationCodeType, "Duration"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs new file mode 100644 index 0000000000..8178fcc102 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -0,0 +1,474 @@ +/* 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/. */ + +use anyhow::{Context, Result}; +use askama::Template; +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; + +use crate::backend::TemplateExpression; +use crate::interface::*; +use crate::BindingsConfig; + +mod callback_interface; +mod compounds; +mod custom; +mod enum_; +mod executor; +mod external; +mod miscellany; +mod object; +mod primitives; +mod record; + +/// A trait tor the implementation. +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String { + self.type_label() + } + + fn literal(&self, _literal: &Literal) -> String { + unimplemented!("Unimplemented for {}", self.type_label()) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option<Vec<String>> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option<String> { + None + } +} + +// Taken from Python's `keyword.py` module. +static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| { + let kwlist = vec![ + "False", + "None", + "True", + "__peg_parser__", + "and", + "as", + "assert", + "async", + "await", + "break", + "class", + "continue", + "def", + "del", + "elif", + "else", + "except", + "finally", + "for", + "from", + "global", + "if", + "import", + "in", + "is", + "lambda", + "nonlocal", + "not", + "or", + "pass", + "raise", + "return", + "try", + "while", + "with", + "yield", + ]; + HashSet::from_iter(kwlist.into_iter().map(|s| s.to_string())) +}); + +// Config options to customize the generated python. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + cdylib_name: Option<String>, + #[serde(default)] + custom_types: HashMap<String, CustomTypeConfig>, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CustomTypeConfig { + // This `CustomTypeConfig` doesn't have a `type_name` like the others -- which is why we have + // separate structs rather than a shared one. + imports: Option<Vec<String>>, + into_custom: TemplateExpression, + from_custom: TemplateExpression, +} + +impl Config { + pub fn cdylib_name(&self) -> String { + if let Some(cdylib_name) = &self.cdylib_name { + cdylib_name.clone() + } else { + "uniffi".into() + } + } +} + +impl BindingsConfig for Config { + fn update_from_ci(&mut self, ci: &ComponentInterface) { + self.cdylib_name + .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); + } + + fn update_from_cdylib_name(&mut self, cdylib_name: &str) { + self.cdylib_name + .get_or_insert_with(|| cdylib_name.to_string()); + } + + fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} +} + +// Generate python bindings for the given ComponentInterface, as a string. +pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> { + PythonWrapper::new(config.clone(), ci) + .render() + .context("failed to render python bindings") +} + +/// A struct to record a Python import statement. +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum ImportRequirement { + /// A simple module import. + Module { mod_name: String }, + /// A single symbol from a module. + Symbol { + mod_name: String, + symbol_name: String, + }, + /// A single symbol from a module with the specified local name. + SymbolAs { + mod_name: String, + symbol_name: String, + as_name: String, + }, +} + +impl ImportRequirement { + /// Render the Python import statement. + fn render(&self) -> String { + match &self { + ImportRequirement::Module { mod_name } => format!("import {mod_name}"), + ImportRequirement::Symbol { + mod_name, + symbol_name, + } => format!("from {mod_name} import {symbol_name}"), + ImportRequirement::SymbolAs { + mod_name, + symbol_name, + as_name, + } => format!("from {mod_name} import {symbol_name} as {as_name}"), + } + } +} + +/// Renders Python helper code for all types +/// +/// This template is a bit different than others in that it stores internal state from the render +/// process. Make sure to only call `render()` once. +#[derive(Template)] +#[template(syntax = "py", escape = "none", path = "Types.py")] +pub struct TypeRenderer<'a> { + python_config: &'a Config, + ci: &'a ComponentInterface, + // Track included modules for the `include_once()` macro + include_once_names: RefCell<HashSet<String>>, + // Track imports added with the `add_import()` macro + imports: RefCell<BTreeSet<ImportRequirement>>, +} + +impl<'a> TypeRenderer<'a> { + fn new(python_config: &'a Config, ci: &'a ComponentInterface) -> Self { + Self { + python_config, + ci, + include_once_names: RefCell::new(HashSet::new()), + imports: RefCell::new(BTreeSet::new()), + } + } + + // The following methods are used by the `Types.py` macros. + + // Helper for the including a template, but only once. + // + // The first time this is called with a name it will return true, indicating that we should + // include the template. Subsequent calls will return false. + fn include_once_check(&self, name: &str) -> bool { + self.include_once_names + .borrow_mut() + .insert(name.to_string()) + } + + // Helper to add an import statement + // + // Call this inside your template to cause an import statement to be added at the top of the + // file. Imports will be sorted and de-deuped. + // + // Returns an empty string so that it can be used inside an askama `{{ }}` block. + fn add_import(&self, name: &str) -> &str { + self.imports.borrow_mut().insert(ImportRequirement::Module { + mod_name: name.to_owned(), + }); + "" + } + + // Like add_import, but arranges for `from module import name`. + fn add_import_of(&self, mod_name: &str, name: &str) -> &str { + self.imports.borrow_mut().insert(ImportRequirement::Symbol { + mod_name: mod_name.to_owned(), + symbol_name: name.to_owned(), + }); + "" + } + + // Like add_import, but arranges for `from module import name as other`. + fn add_import_of_as(&self, mod_name: &str, symbol_name: &str, as_name: &str) -> &str { + self.imports + .borrow_mut() + .insert(ImportRequirement::SymbolAs { + mod_name: mod_name.to_owned(), + symbol_name: symbol_name.to_owned(), + as_name: as_name.to_owned(), + }); + "" + } +} + +#[derive(Template)] +#[template(syntax = "py", escape = "none", path = "wrapper.py")] +pub struct PythonWrapper<'a> { + ci: &'a ComponentInterface, + config: Config, + type_helper_code: String, + type_imports: BTreeSet<ImportRequirement>, +} +impl<'a> PythonWrapper<'a> { + pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { + let type_renderer = TypeRenderer::new(&config, ci); + let type_helper_code = type_renderer.render().unwrap(); + let type_imports = type_renderer.imports.into_inner(); + Self { + config, + ci, + type_helper_code, + type_imports, + } + } + + pub fn imports(&self) -> Vec<ImportRequirement> { + self.type_imports.iter().cloned().collect() + } +} + +fn fixup_keyword(name: String) -> String { + if KEYWORDS.contains(&name) { + format!("_{name}") + } else { + name + } +} + +#[derive(Clone, Default)] +pub struct PythonCodeOracle; + +impl PythonCodeOracle { + fn find(&self, type_: &Type) -> Box<dyn CodeType> { + type_.clone().as_type().as_codetype() + } + + /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). + fn class_name(&self, nm: &str) -> String { + fixup_keyword(nm.to_string().to_upper_camel_case()) + } + + /// Get the idiomatic Python rendering of a function name. + fn fn_name(&self, nm: &str) -> String { + fixup_keyword(nm.to_string().to_snake_case()) + } + + /// Get the idiomatic Python rendering of a variable name. + fn var_name(&self, nm: &str) -> String { + fixup_keyword(nm.to_string().to_snake_case()) + } + + /// Get the idiomatic Python rendering of an individual enum variant. + fn enum_variant_name(&self, nm: &str) -> String { + fixup_keyword(nm.to_string().to_shouty_snake_case()) + } + + fn ffi_type_label(ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::Int8 => "ctypes.c_int8".to_string(), + FfiType::UInt8 => "ctypes.c_uint8".to_string(), + FfiType::Int16 => "ctypes.c_int16".to_string(), + FfiType::UInt16 => "ctypes.c_uint16".to_string(), + FfiType::Int32 => "ctypes.c_int32".to_string(), + FfiType::UInt32 => "ctypes.c_uint32".to_string(), + FfiType::Int64 => "ctypes.c_int64".to_string(), + FfiType::UInt64 => "ctypes.c_uint64".to_string(), + FfiType::Float32 => "ctypes.c_float".to_string(), + FfiType::Float64 => "ctypes.c_double".to_string(), + FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), + FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { + Some(suffix) => format!("_UniffiRustBuffer{suffix}"), + None => "_UniffiRustBuffer".to_string(), + }, + FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), + FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), + // Pointer to an `asyncio.EventLoop` instance + FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), + FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), + FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), + FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), + FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), + } + } +} + +trait AsCodeType { + fn as_codetype(&self) -> Box<dyn CodeType>; +} + +impl<T: AsType> AsCodeType for T { + fn as_codetype(&self) -> Box<dyn CodeType> { + // Map `Type` instances to a `Box<dyn CodeType>` for that type. + // + // There is a companion match in `templates/Types.py` which performs a similar function for the + // template code. + // + // - When adding additional types here, make sure to also add a match arm to the `Types.py` template. + // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + match self.as_type() { + Type::UInt8 => Box::new(primitives::UInt8CodeType), + Type::Int8 => Box::new(primitives::Int8CodeType), + Type::UInt16 => Box::new(primitives::UInt16CodeType), + Type::Int16 => Box::new(primitives::Int16CodeType), + Type::UInt32 => Box::new(primitives::UInt32CodeType), + Type::Int32 => Box::new(primitives::Int32CodeType), + Type::UInt64 => Box::new(primitives::UInt64CodeType), + Type::Int64 => Box::new(primitives::Int64CodeType), + Type::Float32 => Box::new(primitives::Float32CodeType), + Type::Float64 => Box::new(primitives::Float64CodeType), + Type::Boolean => Box::new(primitives::BooleanCodeType), + Type::String => Box::new(primitives::StringCodeType), + Type::Bytes => Box::new(primitives::BytesCodeType), + + Type::Timestamp => Box::new(miscellany::TimestampCodeType), + Type::Duration => Box::new(miscellany::DurationCodeType), + + Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), + Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), + Type::CallbackInterface { name, .. } => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) + } + Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), + Type::Optional { inner_type } => { + Box::new(compounds::OptionalCodeType::new(*inner_type)) + } + Type::Sequence { inner_type } => { + Box::new(compounds::SequenceCodeType::new(*inner_type)) + } + Type::Map { + key_type, + value_type, + } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), + } + } +} + +pub mod filters { + use super::*; + pub use crate::backend::filters::*; + + pub(super) fn type_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().type_label()) + } + + pub(super) fn ffi_converter_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(String::from("_Uniffi") + &as_ct.as_codetype().ffi_converter_name()[3..]) + } + + pub(super) fn canonical_name(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().canonical_name()) + } + + pub(super) fn lift_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.lift", ffi_converter_name(as_ct)?)) + } + + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.lower", ffi_converter_name(as_ct)?)) + } + + pub(super) fn read_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.read", ffi_converter_name(as_ct)?)) + } + + pub(super) fn write_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.write", ffi_converter_name(as_ct)?)) + } + + pub(super) fn literal_py( + literal: &Literal, + as_ct: &impl AsCodeType, + ) -> Result<String, askama::Error> { + Ok(as_ct.as_codetype().literal(literal)) + } + + pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> { + Ok(PythonCodeOracle::ffi_type_label(type_)) + } + + /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). + pub fn class_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.class_name(nm)) + } + + /// Get the idiomatic Python rendering of a function name. + pub fn fn_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.fn_name(nm)) + } + + /// Get the idiomatic Python rendering of a variable name. + pub fn var_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.var_name(nm)) + } + + /// Get the idiomatic Python rendering of an individual enum variant. + pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.enum_variant_name(nm)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs new file mode 100644 index 0000000000..1165bb0e54 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; + +#[derive(Debug)] +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self) -> String { + super::PythonCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs new file mode 100644 index 0000000000..4b3edecad4 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs @@ -0,0 +1,71 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; +use crate::interface::Radix; +use paste::paste; + +fn render_literal(literal: &Literal) -> String { + match literal { + Literal::Boolean(v) => { + if *v { + "True".into() + } else { + "False".into() + } + } + Literal::String(s) => format!("\"{s}\""), + // https://docs.python.org/3/reference/lexical_analysis.html#integer-literals + Literal::Int(i, radix, _) => match radix { + Radix::Octal => format!("int(0o{i:o})"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + Literal::UInt(i, radix, _) => match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + Literal::Float(string, _type_) => string.clone(), + + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $python_name:literal, $canonical_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + impl CodeType for $T { + fn type_label(&self) -> String { + $python_name.into() + } + + fn canonical_name(&self) -> String { + $canonical_name.into() + } + + fn literal(&self, literal: &Literal) -> String { + render_literal(&literal) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "bool", "Bool"); +impl_code_type_for_primitive!(StringCodeType, "str", "String"); +impl_code_type_for_primitive!(BytesCodeType, "bytes", "Bytes"); +impl_code_type_for_primitive!(Int8CodeType, "int", "Int8"); +impl_code_type_for_primitive!(Int16CodeType, "int", "Int16"); +impl_code_type_for_primitive!(Int32CodeType, "int", "Int32"); +impl_code_type_for_primitive!(Int64CodeType, "int", "Int64"); +impl_code_type_for_primitive!(UInt8CodeType, "int", "UInt8"); +impl_code_type_for_primitive!(UInt16CodeType, "int", "UInt16"); +impl_code_type_for_primitive!(UInt32CodeType, "int", "UInt32"); +impl_code_type_for_primitive!(UInt64CodeType, "int", "UInt64"); +impl_code_type_for_primitive!(Float32CodeType, "float", "Float"); +impl_code_type_for_primitive!(Float64CodeType, "float", "Double"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs new file mode 100644 index 0000000000..df00f98e8b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; + +#[derive(Debug)] +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self) -> String { + super::PythonCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, _literal: &Literal) -> String { + unreachable!(); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs new file mode 100644 index 0000000000..4b9dc8f609 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs @@ -0,0 +1,37 @@ +/* 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/. */ + +use std::process::Command; + +use anyhow::Result; +use camino::Utf8Path; +use fs_err as fs; + +pub mod gen_python; +mod test; +use super::super::interface::ComponentInterface; +pub use gen_python::{generate_python_bindings, Config}; +pub use test::{run_script, run_test}; + +// Generate python bindings for the given ComponentInterface, in the given output directory. +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<()> { + let py_file = out_dir.join(format!("{}.py", ci.namespace())); + fs::write(&py_file, generate_python_bindings(config, ci)?)?; + + if try_format_code { + if let Err(e) = Command::new("yapf").arg(&py_file).output() { + println!( + "Warning: Unable to auto-format {} using yapf: {e:?}", + py_file.file_name().unwrap(), + ) + } + } + + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py new file mode 100644 index 0000000000..82aa534b46 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py @@ -0,0 +1,40 @@ +# RustFuturePoll values +_UNIFFI_RUST_FUTURE_POLL_READY = 0 +_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 + +# Stores futures for _uniffi_continuation_callback +_UniffiContinuationPointerManager = _UniffiPointerManager() + +# Continuation callback for async functions +# lift the return value or error and resolve the future, causing the async function to resume. +@_UNIFFI_FUTURE_CONTINUATION_T +def _uniffi_continuation_callback(future_ptr, poll_code): + (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr) + eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) + +def _uniffi_set_future_result(future, poll_code): + if not future.cancelled(): + future.set_result(poll_code) + +async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): + try: + eventloop = asyncio.get_running_loop() + + # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value + while True: + future = eventloop.create_future() + ffi_poll( + rust_future, + _UniffiContinuationPointerManager.new_pointer((eventloop, future)), + ) + poll_code = await future + if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: + break + + return lift_func( + _rust_call_with_error(error_ffi_converter, ffi_complete, rust_future) + ) + finally: + ffi_free(rust_future) + +_UniffiLib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(_uniffi_continuation_callback) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py new file mode 100644 index 0000000000..6775e9e132 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py @@ -0,0 +1,16 @@ +class _UniffiConverterBool(_UniffiConverterPrimitive): + @classmethod + def check(cls, value): + return not not value + + @classmethod + def read(cls, buf): + return cls.lift(buf.read_u8()) + + @classmethod + def write_unchecked(cls, value, buf): + buf.write_u8(value) + + @staticmethod + def lift(value): + return value != 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py new file mode 100644 index 0000000000..196b5b29fa --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py @@ -0,0 +1,16 @@ +class _UniffiConverterBytes(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + size = buf.read_i32() + if size < 0: + raise InternalError("Unexpected negative byte string length") + return buf.read(size) + + @staticmethod + def write(value, buf): + try: + memoryview(value) + except TypeError: + raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) + buf.write_i32(len(value)) + buf.write(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py new file mode 100644 index 0000000000..0fe2ab8dc0 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py @@ -0,0 +1,77 @@ +import threading + +class ConcurrentHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._left_map = {} # type: Dict[Handle, Any] + self._right_map = {} # type: Dict[Any, Handle] + + self._lock = threading.Lock() + self._current_handle = 0 + self._stride = 1 + + + def insert(self, obj): + with self._lock: + if obj in self._right_map: + return self._right_map[obj] + else: + handle = self._current_handle + self._current_handle += self._stride + self._left_map[handle] = obj + self._right_map[obj] = handle + return handle + + def get(self, handle): + with self._lock: + return self._left_map.get(handle) + + def remove(self, handle): + with self._lock: + if handle in self._left_map: + obj = self._left_map.pop(handle) + del self._right_map[obj] + return obj + +# Magic number for the Rust proxy to call using the same mechanism as every other method, +# to free the callback once it's dropped by Rust. +IDX_CALLBACK_FREE = 0 +# Return codes for callback calls +_UNIFFI_CALLBACK_SUCCESS = 0 +_UNIFFI_CALLBACK_ERROR = 1 +_UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 + +class _UniffiConverterCallbackInterface: + _handle_map = ConcurrentHandleMap() + + def __init__(self, cb): + self._foreign_callback = cb + + def drop(self, handle): + self.__class__._handle_map.remove(handle) + + @classmethod + def lift(cls, handle): + obj = cls._handle_map.get(handle) + if not obj: + raise InternalError("The object in the handle map has been dropped already") + + return obj + + @classmethod + def read(cls, buf): + handle = buf.read_u64() + cls.lift(handle) + + @classmethod + def lower(cls, cb): + handle = cls._handle_map.insert(cb) + return handle + + @classmethod + def write(cls, cb, buf): + buf.write_u64(cls.lower(cb)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py new file mode 100644 index 0000000000..e0e926757a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -0,0 +1,105 @@ +{%- let cbi = ci|get_callback_interface_definition(id) %} +{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} + +{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} + +# Declaration and _UniffiConverters for {{ type_name }} Callback Interface + +class {{ type_name }}: + {% for meth in cbi.methods() -%} + def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): + raise NotImplementedError + + {% endfor %} + +def py_{{ foreign_callback }}(handle, method, args_data, args_len, buf_ptr): + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name %} + def {{ method_name }}(python_callback, args_stream, buf_ptr): + {#- Unpacking args from the _UniffiRustBuffer #} + def makeCall(): + {#- Calling the concrete callback object #} + {%- if meth.arguments().len() != 0 -%} + return python_callback.{{ meth.name()|fn_name }}( + {% for arg in meth.arguments() -%} + {{ arg|read_fn }}(args_stream) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ) + {%- else %} + return python_callback.{{ meth.name()|fn_name }}() + {%- endif %} + + def makeCallAndHandleReturn(): + {%- match meth.return_type() %} + {%- when Some(return_type) %} + rval = makeCall() + with _UniffiRustBuffer.alloc_with_builder() as builder: + {{ return_type|write_fn }}(rval, builder) + buf_ptr[0] = builder.finalize() + {%- when None %} + makeCall() + {%- endmatch %} + return _UNIFFI_CALLBACK_SUCCESS + + {%- match meth.throws_type() %} + {%- when None %} + return makeCallAndHandleReturn() + {%- when Some(err) %} + try: + return makeCallAndHandleReturn() + except {{ err|type_name }} as e: + # Catch errors declared in the UDL file + with _UniffiRustBuffer.alloc_with_builder() as builder: + {{ err|write_fn }}(e, builder) + buf_ptr[0] = builder.finalize() + return _UNIFFI_CALLBACK_ERROR + {%- endmatch %} + + {% endfor %} + + cb = {{ ffi_converter_name }}.lift(handle) + if not cb: + raise InternalError("No callback in handlemap; this is a uniffi bug") + + if method == IDX_CALLBACK_FREE: + {{ ffi_converter_name }}.drop(handle) + # Successfull return + # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + return _UNIFFI_CALLBACK_SUCCESS + + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + if method == {{ loop.index }}: + # Call the method and handle any errors + # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details + try: + return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr) + except BaseException as e: + # Catch unexpected errors + try: + # Try to serialize the exception into a String + buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e)) + except: + # If that fails, just give up + pass + return _UNIFFI_CALLBACK_UNEXPECTED_ERROR + {% endfor %} + + # This should never happen, because an out of bounds method index won't + # ever be used. Once we can catch errors, we should return an InternalException. + # https://github.com/mozilla/uniffi-rs/issues/351 + + # An unexpected error happened. + # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + return _UNIFFI_CALLBACK_UNEXPECTED_ERROR + +# We need to keep this function reference alive: +# if they get GC'd while in use then UniFFI internals could attempt to call a function +# that is in freed memory. +# That would be...uh...bad. Yeah, that's the word. Bad. +{{ foreign_callback }} = _UNIFFI_FOREIGN_CALLBACK_T(py_{{ foreign_callback }}) +_rust_call(lambda err: _UniffiLib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)) + +# The _UniffiConverter which transforms the Callbacks in to Handles to pass to Rust. +{{ ffi_converter_name }} = _UniffiConverterCallbackInterface({{ foreign_callback }}) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py new file mode 100644 index 0000000000..5be6155b84 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py @@ -0,0 +1,58 @@ +{%- match python_config.custom_types.get(name.as_str()) %} +{% when None %} +{#- No custom type config, just forward all methods to our builtin type #} +# Type alias +{{ name }} = {{ builtin|type_name }} + +class _UniffiConverterType{{ name }}: + @staticmethod + def write(value, buf): + {{ builtin|ffi_converter_name }}.write(value, buf) + + @staticmethod + def read(buf): + return {{ builtin|ffi_converter_name }}.read(buf) + + @staticmethod + def lift(value): + return {{ builtin|ffi_converter_name }}.lift(value) + + @staticmethod + def lower(value): + return {{ builtin|ffi_converter_name }}.lower(value) + +{%- when Some(config) %} + +{%- match config.imports %} +{%- when Some(imports) %} +{%- for import_name in imports %} +{{ self.add_import(import_name) }} +{%- endfor %} +{%- else %} +{%- endmatch %} + +# Type alias +{{ name }} = {{ builtin|type_name }} + +{#- Custom type config supplied, use it to convert the builtin type #} +class _UniffiConverterType{{ name }}: + @staticmethod + def write(value, buf): + builtin_value = {{ config.from_custom.render("value") }} + {{ builtin|write_fn }}(builtin_value, buf) + + @staticmethod + def read(buf): + builtin_value = {{ builtin|read_fn }}(buf) + return {{ config.into_custom.render("builtin_value") }} + + @staticmethod + def lift(value): + builtin_value = {{ builtin|lift_fn }}(value) + return {{ config.into_custom.render("builtin_value") }} + + @staticmethod + def lower(value): + builtin_value = {{ config.from_custom.render("value") }} + return {{ builtin|lower_fn }}(builtin_value) +{%- endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py new file mode 100644 index 0000000000..10974e009d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py @@ -0,0 +1,21 @@ +# The Duration type. +Duration = datetime.timedelta + +# There is a loss of precision when converting from Rust durations, +# which are accurate to the nanosecond, +# to Python durations, which are only accurate to the microsecond. +class _UniffiConverterDuration(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + seconds = buf.read_u64() + microseconds = buf.read_u32() / 1.0e3 + return datetime.timedelta(seconds=seconds, microseconds=microseconds) + + @staticmethod + def write(value, buf): + seconds = value.seconds + value.days * 24 * 3600 + nanoseconds = value.microseconds * 1000 + if seconds < 0: + raise ValueError("Invalid duration, must be non-negative") + buf.write_i64(seconds) + buf.write_u32(nanoseconds) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py new file mode 100644 index 0000000000..84d089baf9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py @@ -0,0 +1,97 @@ +{# +# Python has a built-in `enum` module which is nice to use, but doesn't support +# variants with associated data. So, we switch here, and generate a stdlib `enum` +# when none of the variants have associated data, or a generic nested-class +# construct when they do. +#} +{% if e.is_flat() %} + +class {{ type_name }}(enum.Enum): + {% for variant in e.variants() -%} + {{ variant.name()|enum_variant_py }} = {{ loop.index }} + {% endfor %} +{% else %} + +class {{ type_name }}: + def __init__(self): + raise RuntimeError("{{ type_name }} cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + {% for variant in e.variants() -%} + class {{ variant.name()|enum_variant_py }}: + {% for field in variant.fields() %} + {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- endfor %} + + @typing.no_type_check + def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}): + {% if variant.has_fields() %} + {%- for field in variant.fields() %} + self.{{ field.name()|var_name }} = {{ field.name()|var_name }} + {%- endfor %} + {% else %} + pass + {% endif %} + + def __str__(self): + return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + + def __eq__(self, other): + if not other.is_{{ variant.name()|var_name }}(): + return False + {%- for field in variant.fields() %} + if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}: + return False + {%- endfor %} + return True + {% endfor %} + + # For each variant, we have an `is_NAME` method for easily checking + # whether an instance is that variant. + {% for variant in e.variants() -%} + def is_{{ variant.name()|var_name }}(self) -> bool: + return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }}) + {% endfor %} + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +{% for variant in e.variants() -%} +{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {}) # type: ignore +{% endfor %} + +{% endif %} + +class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + + {%- for variant in e.variants() %} + if variant == {{ loop.index }}: + {%- if e.is_flat() %} + return {{ type_name }}.{{variant.name()|enum_variant_py}} + {%- else %} + return {{ type_name }}.{{variant.name()|enum_variant_py}}( + {%- for field in variant.fields() %} + {{ field|read_fn }}(buf), + {%- endfor %} + ) + {%- endif %} + {%- endfor %} + raise InternalError("Raw enum value doesn't match any cases") + + def write(value, buf): + {%- for variant in e.variants() %} + {%- if e.is_flat() %} + if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + buf.write_i32({{ loop.index }}) + {%- else %} + if value.is_{{ variant.name()|var_name }}(): + buf.write_i32({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + {%- endif %} + {%- endfor %} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py new file mode 100644 index 0000000000..26a1e6452a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -0,0 +1,70 @@ +# {{ type_name }} +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class {{ type_name }}(Exception): + pass + +_UniffiTemp{{ type_name }} = {{ type_name }} + +class {{ type_name }}: # type: ignore + {%- for variant in e.variants() -%} + {%- let variant_type_name = variant.name()|class_name -%} + {%- if e.is_flat() %} + class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + def __repr__(self): + return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self))) + {%- else %} + class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}): + {%- if variant.has_fields() %} + super().__init__(", ".join([ + {%- for field in variant.fields() %} + "{{ field.name()|var_name }}={!r}".format({{ field.name()|var_name }}), + {%- endfor %} + ])) + {%- for field in variant.fields() %} + self.{{ field.name()|var_name }} = {{ field.name()|var_name }} + {%- endfor %} + {%- else %} + pass + {%- endif %} + def __repr__(self): + return "{{ type_name }}.{{ variant_type_name }}({})".format(str(self)) + {%- endif %} + _UniffiTemp{{ type_name }}.{{ variant_type_name }} = {{ variant_type_name }} # type: ignore + {%- endfor %} + +{{ type_name }} = _UniffiTemp{{ type_name }} # type: ignore +del _UniffiTemp{{ type_name }} + + +class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + {%- for variant in e.variants() %} + if variant == {{ loop.index }}: + return {{ type_name }}.{{ variant.name()|class_name }}( + {%- if e.is_flat() %} + {{ Type::String.borrow()|read_fn }}(buf), + {%- else %} + {%- for field in variant.fields() %} + {{ field.name()|var_name }}={{ field|read_fn }}(buf), + {%- endfor %} + {%- endif %} + ) + {%- endfor %} + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def write(value, buf): + {%- for variant in e.variants() %} + if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + buf.write_i32({{ loop.index }}) + {%- for field in variant.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} + {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py new file mode 100644 index 0000000000..71e05e8b06 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py @@ -0,0 +1,9 @@ +{%- let ns = namespace|fn_name %} + +# External type {{name}} is in namespace "{{namespace}}", crate {{module_path}} +{%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %} +{{ self.add_import_of(ns, ffi_converter_name) }} +{{ self.add_import_of(ns, name) }} {#- import the type alias itself -#} + +{%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %} +{{ self.add_import_of_as(ns, "_UniffiRustBuffer", rustbuffer_local_name) }} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py new file mode 100644 index 0000000000..a52107a638 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py @@ -0,0 +1,8 @@ +class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_float() + + @staticmethod + def write_unchecked(value, buf): + buf.write_float(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py new file mode 100644 index 0000000000..772f5080e9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py @@ -0,0 +1,8 @@ +class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_double() + + @staticmethod + def write_unchecked(value, buf): + buf.write_double(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py new file mode 100644 index 0000000000..6a6932fed0 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py @@ -0,0 +1,63 @@ +# FFI code for the ForeignExecutor type + +{{ self.add_import("asyncio") }} + +_UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0 +_UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1 +_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0 +_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED = 1 +_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2 + +class {{ ffi_converter_name }}: + _pointer_manager = _UniffiPointerManager() + + @classmethod + def lower(cls, eventloop): + if not isinstance(eventloop, asyncio.BaseEventLoop): + raise TypeError("_uniffi_executor_callback: Expected EventLoop instance") + return cls._pointer_manager.new_pointer(eventloop) + + @classmethod + def write(cls, eventloop, buf): + buf.write_c_size_t(cls.lower(eventloop)) + + @classmethod + def read(cls, buf): + return cls.lift(buf.read_c_size_t()) + + @classmethod + def lift(cls, value): + return cls._pointer_manager.lookup(value) + +@_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T +def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data): + if task_ptr is None: + {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address) + return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + else: + eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address) + if eventloop.is_closed(): + return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED + + callback = _UNIFFI_RUST_TASK(task_ptr) + # FIXME: there's no easy way to get a callback when an eventloop is closed. This means that + # if eventloop is called before the `call_soon_threadsafe()` calls are invoked, the call + # will never happen and we will probably leak a resource. + if delay == 0: + # This can be called from any thread, so make sure to use `call_soon_threadsafe' + eventloop.call_soon_threadsafe(callback, task_data, + _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) + else: + # For delayed tasks, we use `call_soon_threadsafe()` + `call_later()` to make the + # operation threadsafe + eventloop.call_soon_threadsafe(eventloop.call_later, delay / 1000.0, callback, + task_data, _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) + return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + +# Register the callback with the scaffolding +{%- match ci.ffi_foreign_executor_callback_set() %} +{%- when Some with (fn) %} +_UniffiLib.{{ fn.name() }}(_uniffi_executor_callback) +{%- when None %} +{#- No foreign executor, we don't set anything #} +{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py new file mode 100644 index 0000000000..dca962f176 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -0,0 +1,75 @@ +# A handful of classes and functions to support the generated data structures. +# This would be a good candidate for isolating in its own ffi-support lib. + +class InternalError(Exception): + pass + +class _UniffiRustCallStatus(ctypes.Structure): + """ + Error runtime. + """ + _fields_ = [ + ("code", ctypes.c_int8), + ("error_buf", _UniffiRustBuffer), + ] + + # These match the values from the uniffi::rustcalls module + CALL_SUCCESS = 0 + CALL_ERROR = 1 + CALL_PANIC = 2 + + def __str__(self): + if self.code == _UniffiRustCallStatus.CALL_SUCCESS: + return "_UniffiRustCallStatus(CALL_SUCCESS)" + elif self.code == _UniffiRustCallStatus.CALL_ERROR: + return "_UniffiRustCallStatus(CALL_ERROR)" + elif self.code == _UniffiRustCallStatus.CALL_PANIC: + return "_UniffiRustCallStatus(CALL_PANIC)" + else: + return "_UniffiRustCallStatus(<invalid code>)" + +def _rust_call(fn, *args): + # Call a rust function + return _rust_call_with_error(None, fn, *args) + +def _rust_call_with_error(error_ffi_converter, fn, *args): + # Call a rust function and handle any errors + # + # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. + # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. + call_status = _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer(0, 0, None)) + + args_with_error = args + (ctypes.byref(call_status),) + result = fn(*args_with_error) + _uniffi_check_call_status(error_ffi_converter, call_status) + return result + +def _uniffi_check_call_status(error_ffi_converter, call_status): + if call_status.code == _UniffiRustCallStatus.CALL_SUCCESS: + pass + elif call_status.code == _UniffiRustCallStatus.CALL_ERROR: + if error_ffi_converter is None: + call_status.error_buf.free() + raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") + else: + raise error_ffi_converter.lift(call_status.error_buf) + elif call_status.code == _UniffiRustCallStatus.CALL_PANIC: + # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer + # with the message. But if that code panics, then it just sends back + # an empty buffer. + if call_status.error_buf.len > 0: + msg = _UniffiConverterString.lift(call_status.error_buf) + else: + msg = "Unknown rust panic" + raise InternalError(msg) + else: + raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( + call_status.code)) + +# A function pointer for a callback as defined by UniFFI. +# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int` +_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer)) + +# UniFFI future continuation +_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8) + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py new file mode 100644 index 0000000000..99f19dc1c0 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterInt16(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i16" + VALUE_MIN = -2**15 + VALUE_MAX = 2**15 + + @staticmethod + def read(buf): + return buf.read_i16() + + @staticmethod + def write_unchecked(value, buf): + buf.write_i16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py new file mode 100644 index 0000000000..3b142c8749 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterInt32(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i32" + VALUE_MIN = -2**31 + VALUE_MAX = 2**31 + + @staticmethod + def read(buf): + return buf.read_i32() + + @staticmethod + def write_unchecked(value, buf): + buf.write_i32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py new file mode 100644 index 0000000000..6e94379cbf --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterInt64(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i64" + VALUE_MIN = -2**63 + VALUE_MAX = 2**63 + + @staticmethod + def read(buf): + return buf.read_i64() + + @staticmethod + def write_unchecked(value, buf): + buf.write_i64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py new file mode 100644 index 0000000000..732530e3cb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterInt8(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i8" + VALUE_MIN = -2**7 + VALUE_MAX = 2**7 + + @staticmethod + def read(buf): + return buf.read_i8() + + @staticmethod + def write_unchecked(value, buf): + buf.write_i8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py new file mode 100644 index 0000000000..387227ed09 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py @@ -0,0 +1,27 @@ +{%- let key_ffi_converter = key_type|ffi_converter_name %} +{%- let value_ffi_converter = value_type|ffi_converter_name %} + +class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @classmethod + def write(cls, items, buf): + buf.write_i32(len(items)) + for (key, value) in items.items(): + {{ key_ffi_converter }}.write(key, buf) + {{ value_ffi_converter }}.write(value, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative map size") + + # It would be nice to use a dict comprehension, + # but in Python 3.7 and before the evaluation order is not according to spec, + # so we we're reading the value before the key. + # This loop makes the order explicit: first reading the key, then the value. + d = {} + for i in range(count): + key = {{ key_ffi_converter }}.read(buf) + val = {{ value_ffi_converter }}.read(buf) + d[key] = val + return d diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py new file mode 100644 index 0000000000..fac6cd5564 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -0,0 +1,83 @@ +# Define some ctypes FFI types that we use in the library + +""" +ctypes type for the foreign executor callback. This is a built-in interface for scheduling +tasks + +Args: + executor: opaque c_size_t value representing the eventloop + delay: delay in ms + task: function pointer to the task callback + task_data: void pointer to the task callback data + +Normally we should call task(task_data) after the detail. +However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should +decrease the EventLoop refcount. +""" +_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p) + +""" +Function pointer for a Rust task, which a callback function that takes a opaque pointer +""" +_UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) + +def _uniffi_future_callback_t(return_type): + """ + Factory function to create callback function types for async functions + """ + return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus) + +def _uniffi_load_indirect(): + """ + This is how we find and load the dynamic library provided by the component. + For now we just look it up by name. + """ + if sys.platform == "darwin": + libname = "lib{}.dylib" + elif sys.platform.startswith("win"): + # As of python3.8, ctypes does not seem to search $PATH when loading DLLs. + # We could use `os.add_dll_directory` to configure the search path, but + # it doesn't feel right to mess with application-wide settings. Let's + # assume that the `.dll` is next to the `.py` file and load by full path. + libname = os.path.join( + os.path.dirname(__file__), + "{}.dll", + ) + else: + # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos + libname = "lib{}.so" + + libname = libname.format("{{ config.cdylib_name() }}") + path = os.path.join(os.path.dirname(__file__), libname) + lib = ctypes.cdll.LoadLibrary(path) + return lib + +def _uniffi_check_contract_api_version(lib): + # Get the bindings contract version from our ComponentInterface + bindings_contract_version = {{ ci.uniffi_contract_version() }} + # Get the scaffolding contract version by calling the into the dylib + scaffolding_contract_version = lib.{{ ci.ffi_uniffi_contract_version().name() }}() + if bindings_contract_version != scaffolding_contract_version: + raise InternalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + +def _uniffi_check_api_checksums(lib): + {%- for (name, expected_checksum) in ci.iter_checksums() %} + if lib.{{ name }}() != {{ expected_checksum }}: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + {%- else %} + pass + {%- endfor %} + +# A ctypes library to expose the extern-C FFI definitions. +# This is an implementation detail which will be called internally by the public API. + +_UniffiLib = _uniffi_load_indirect() +{%- for func in ci.iter_ffi_function_definitions() %} +_UniffiLib.{{ func.name() }}.argtypes = ( + {%- call py::arg_list_ffi_decl(func) -%} +) +_UniffiLib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %} +{%- endfor %} +{# Ensure to call the contract verification only after we defined all functions. -#} +_uniffi_check_contract_api_version(_UniffiLib) +_uniffi_check_api_checksums(_UniffiLib) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py new file mode 100644 index 0000000000..7e98f7c46f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -0,0 +1,87 @@ +{%- let obj = ci|get_object_definition(name) %} + +class {{ type_name }}: + _pointer: ctypes.c_void_p + +{%- match obj.primary_constructor() %} +{%- when Some with (cons) %} + def __init__(self, {% call py::arg_list_decl(cons) -%}): + {%- call py::setup_args_extra_indent(cons) %} + self._pointer = {% call py::to_ffi_call(cons) %} +{%- when None %} +{%- endmatch %} + + def __del__(self): + # In case of partial initialization of instances. + pointer = getattr(self, "_pointer", None) + if pointer is not None: + _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer) + + # Used by alternative constructors or any methods which return this type. + @classmethod + def _make_instance_(cls, pointer): + # Lightly yucky way to bypass the usual __init__ logic + # and just create a new instance with the required pointer. + inst = cls.__new__(cls) + inst._pointer = pointer + return inst + +{%- for cons in obj.alternate_constructors() %} + + @classmethod + def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::setup_args_extra_indent(cons) %} + # Call the (fallible) function before creating any half-baked object instances. + pointer = {% call py::to_ffi_call(cons) %} + return cls._make_instance_(pointer) +{% endfor %} + +{%- for meth in obj.methods() -%} + {%- call py::method_decl(meth.name()|fn_name, meth) %} +{% endfor %} + +{%- for tm in obj.uniffi_traits() -%} +{%- match tm %} +{%- when UniffiTrait::Debug { fmt } %} + {%- call py::method_decl("__repr__", fmt) %} +{%- when UniffiTrait::Display { fmt } %} + {%- call py::method_decl("__str__", fmt) %} +{%- when UniffiTrait::Eq { eq, ne } %} + def __eq__(self, other: object) -> {{ eq.return_type().unwrap()|type_name }}: + if not isinstance(other, {{ type_name }}): + return NotImplemented + + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %}) + + def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: + if not isinstance(other, {{ type_name }}): + return NotImplemented + + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %}) +{%- when UniffiTrait::Hash { hash } %} + {%- call py::method_decl("__hash__", hash) %} +{% endmatch %} +{% endfor %} + + +class {{ ffi_converter_name }}: + @classmethod + def read(cls, buf): + ptr = buf.read_u64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value, buf): + if not isinstance(value, {{ type_name }}): + raise TypeError("Expected {{ type_name }} instance, {} found".format(type(value).__name__)) + buf.write_u64(cls.lower(value)) + + @staticmethod + def lift(value): + return {{ type_name }}._make_instance_(value) + + @staticmethod + def lower(value): + return value._pointer diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py new file mode 100644 index 0000000000..e406c51d49 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py @@ -0,0 +1,21 @@ +{%- let inner_ffi_converter = inner_type|ffi_converter_name %} + +class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + {{ inner_ffi_converter }}.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return {{ inner_ffi_converter }}.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py new file mode 100644 index 0000000000..23aa28eab4 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py @@ -0,0 +1,68 @@ +class _UniffiPointerManagerCPython: + """ + Manage giving out pointers to Python objects on CPython + + This class is used to generate opaque pointers that reference Python objects to pass to Rust. + It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative. + """ + + def new_pointer(self, obj): + """ + Get a pointer for an object as a ctypes.c_size_t instance + + Each call to new_pointer() must be balanced with exactly one call to release_pointer() + + This returns a ctypes.c_size_t. This is always the same size as a pointer and can be + interchanged with pointers for FFI function arguments and return values. + """ + # IncRef the object since we're going to pass a pointer to Rust + ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj)) + # id() is the object address on CPython + # (https://docs.python.org/3/library/functions.html#id) + return id(obj) + + def release_pointer(self, address): + py_obj = ctypes.cast(address, ctypes.py_object) + obj = py_obj.value + ctypes.pythonapi.Py_DecRef(py_obj) + return obj + + def lookup(self, address): + return ctypes.cast(address, ctypes.py_object).value + +class _UniffiPointerManagerGeneral: + """ + Manage giving out pointers to Python objects on non-CPython platforms + + This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on + CPython and is slightly slower. + + Instead of using real pointers, it maps integer values to objects and returns the keys as + c_size_t values. + """ + + def __init__(self): + self._map = {} + self._lock = threading.Lock() + self._current_handle = 0 + + def new_pointer(self, obj): + with self._lock: + handle = self._current_handle + self._current_handle += 1 + self._map[handle] = obj + return handle + + def release_pointer(self, handle): + with self._lock: + return self._map.pop(handle) + + def lookup(self, handle): + with self._lock: + return self._map[handle] + +# Pick an pointer manager implementation based on the platform +if platform.python_implementation() == 'CPython': + _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore +else: + _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py new file mode 100644 index 0000000000..99a30e120f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -0,0 +1,49 @@ +{%- let rec = ci|get_record_definition(name) %} +class {{ type_name }}: + {% for field in rec.fields() %} + {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- endfor %} + + @typing.no_type_check + def __init__(self, {% for field in rec.fields() %} + {{- field.name()|var_name }}: "{{- field|type_name }}" + {%- if field.default_value().is_some() %} = _DEFAULT{% endif %} + {%- if !loop.last %}, {% endif %} + {%- endfor %}): + {%- for field in rec.fields() %} + {%- let field_name = field.name()|var_name %} + {%- match field.default_value() %} + {%- when None %} + self.{{ field_name }} = {{ field_name }} + {%- when Some with(literal) %} + if {{ field_name }} is _DEFAULT: + self.{{ field_name }} = {{ literal|literal_py(field) }} + else: + self.{{ field_name }} = {{ field_name }} + {%- endmatch %} + {%- endfor %} + + def __str__(self): + return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + + def __eq__(self, other): + {%- for field in rec.fields() %} + if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}: + return False + {%- endfor %} + return True + +class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return {{ type_name }}( + {%- for field in rec.fields() %} + {{ field.name()|var_name }}={{ field|read_fn }}(buf), + {%- endfor %} + ) + + @staticmethod + def write(value, buf): + {%- for field in rec.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py new file mode 100644 index 0000000000..daabd5b4b9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py @@ -0,0 +1,59 @@ +# Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. +class _UniffiConverterPrimitive: + @classmethod + def check(cls, value): + return value + + @classmethod + def lift(cls, value): + return value + + @classmethod + def lower(cls, value): + return cls.lowerUnchecked(cls.check(value)) + + @classmethod + def lowerUnchecked(cls, value): + return value + + @classmethod + def write(cls, value, buf): + cls.write_unchecked(cls.check(value), buf) + +class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): + @classmethod + def check(cls, value): + try: + value = value.__index__() + except Exception: + raise TypeError("'{}' object cannot be interpreted as an integer".format(type(value).__name__)) + if not isinstance(value, int): + raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) + if not cls.VALUE_MIN <= value < cls.VALUE_MAX: + raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) + return super().check(value) + +class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): + @classmethod + def check(cls, value): + try: + value = value.__float__() + except Exception: + raise TypeError("must be real number, not {}".format(type(value).__name__)) + if not isinstance(value, float): + raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) + return super().check(value) + +# Helper class for wrapper types that will always go through a _UniffiRustBuffer. +# Classes should inherit from this and implement the `read` and `write` static methods. +class _UniffiConverterRustBuffer: + @classmethod + def lift(cls, rbuf): + with rbuf.consume_with_stream() as stream: + return cls.read(stream) + + @classmethod + def lower(cls, value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + cls.write(value, builder) + return builder.finalize() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py new file mode 100644 index 0000000000..c317a632fc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py @@ -0,0 +1,211 @@ + +class _UniffiRustBuffer(ctypes.Structure): + _fields_ = [ + ("capacity", ctypes.c_int32), + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + @staticmethod + def alloc(size): + return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}, size) + + @staticmethod + def reserve(rbuf, additional): + return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional) + + def free(self): + return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_free().name() }}, self) + + def __str__(self): + return "_UniffiRustBuffer(capacity={}, len={}, data={})".format( + self.capacity, + self.len, + self.data[0:self.len] + ) + + @contextlib.contextmanager + def alloc_with_builder(*args): + """Context-manger to allocate a buffer using a _UniffiRustBufferBuilder. + + The allocated buffer will be automatically freed if an error occurs, ensuring that + we don't accidentally leak it. + """ + builder = _UniffiRustBufferBuilder() + try: + yield builder + except: + builder.discard() + raise + + @contextlib.contextmanager + def consume_with_stream(self): + """Context-manager to consume a buffer using a _UniffiRustBufferStream. + + The _UniffiRustBuffer will be freed once the context-manager exits, ensuring that we don't + leak it even if an error occurs. + """ + try: + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of consume_with_stream") + finally: + self.free() + + @contextlib.contextmanager + def read_with_stream(self): + """Context-manager to read a buffer using a _UniffiRustBufferStream. + + This is like consume_with_stream, but doesn't free the buffer afterwards. + It should only be used with borrowed `_UniffiRustBuffer` data. + """ + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of read_with_stream") + +class _UniffiForeignBytes(ctypes.Structure): + _fields_ = [ + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + def __str__(self): + return "_UniffiForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len]) + + +class _UniffiRustBufferStream: + """ + Helper for structured reading of bytes from a _UniffiRustBuffer + """ + + def __init__(self, data, len): + self.data = data + self.len = len + self.offset = 0 + + @classmethod + def from_rust_buffer(cls, buf): + return cls(buf.data, buf.len) + + def remaining(self): + return self.len - self.offset + + def _unpack_from(self, size, format): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + value = struct.unpack(format, self.data[self.offset:self.offset+size])[0] + self.offset += size + return value + + def read(self, size): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + data = self.data[self.offset:self.offset+size] + self.offset += size + return data + + def read_i8(self): + return self._unpack_from(1, ">b") + + def read_u8(self): + return self._unpack_from(1, ">B") + + def read_i16(self): + return self._unpack_from(2, ">h") + + def read_u16(self): + return self._unpack_from(2, ">H") + + def read_i32(self): + return self._unpack_from(4, ">i") + + def read_u32(self): + return self._unpack_from(4, ">I") + + def read_i64(self): + return self._unpack_from(8, ">q") + + def read_u64(self): + return self._unpack_from(8, ">Q") + + def read_float(self): + v = self._unpack_from(4, ">f") + return v + + def read_double(self): + return self._unpack_from(8, ">d") + + def read_c_size_t(self): + return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N") + +class _UniffiRustBufferBuilder: + """ + Helper for structured writing of bytes into a _UniffiRustBuffer. + """ + + def __init__(self): + self.rbuf = _UniffiRustBuffer.alloc(16) + self.rbuf.len = 0 + + def finalize(self): + rbuf = self.rbuf + self.rbuf = None + return rbuf + + def discard(self): + if self.rbuf is not None: + rbuf = self.finalize() + rbuf.free() + + @contextlib.contextmanager + def _reserve(self, num_bytes): + if self.rbuf.len + num_bytes > self.rbuf.capacity: + self.rbuf = _UniffiRustBuffer.reserve(self.rbuf, num_bytes) + yield None + self.rbuf.len += num_bytes + + def _pack_into(self, size, format, value): + with self._reserve(size): + # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out. + for i, byte in enumerate(struct.pack(format, value)): + self.rbuf.data[self.rbuf.len + i] = byte + + def write(self, value): + with self._reserve(len(value)): + for i, byte in enumerate(value): + self.rbuf.data[self.rbuf.len + i] = byte + + def write_i8(self, v): + self._pack_into(1, ">b", v) + + def write_u8(self, v): + self._pack_into(1, ">B", v) + + def write_i16(self, v): + self._pack_into(2, ">h", v) + + def write_u16(self, v): + self._pack_into(2, ">H", v) + + def write_i32(self, v): + self._pack_into(4, ">i", v) + + def write_u32(self, v): + self._pack_into(4, ">I", v) + + def write_i64(self, v): + self._pack_into(8, ">q", v) + + def write_u64(self, v): + self._pack_into(8, ">Q", v) + + def write_float(self, v): + self._pack_into(4, ">f", v) + + def write_double(self, v): + self._pack_into(8, ">d", v) + + def write_c_size_t(self, v): + self._pack_into(ctypes.sizeof(ctypes.c_size_t) , "@N", v) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py new file mode 100644 index 0000000000..3c9f5a4596 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py @@ -0,0 +1,19 @@ +{%- let inner_ffi_converter = inner_type|ffi_converter_name %} + +class {{ ffi_converter_name}}(_UniffiConverterRustBuffer): + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + {{ inner_ffi_converter }}.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + {{ inner_ffi_converter }}.read(buf) for i in range(count) + ] diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py new file mode 100644 index 0000000000..40890b6abc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py @@ -0,0 +1,33 @@ +class _UniffiConverterString: + @staticmethod + def check(value): + if not isinstance(value, str): + raise TypeError("argument must be str, not {}".format(type(value).__name__)) + return value + + @staticmethod + def read(buf): + size = buf.read_i32() + if size < 0: + raise InternalError("Unexpected negative string length") + utf8_bytes = buf.read(size) + return utf8_bytes.decode("utf-8") + + @staticmethod + def write(value, buf): + value = _UniffiConverterString.check(value) + utf8_bytes = value.encode("utf-8") + buf.write_i32(len(utf8_bytes)) + buf.write(utf8_bytes) + + @staticmethod + def lift(buf): + with buf.consume_with_stream() as stream: + return stream.read(stream.remaining()).decode("utf-8") + + @staticmethod + def lower(value): + value = _UniffiConverterString.check(value) + with _UniffiRustBuffer.alloc_with_builder() as builder: + builder.write(value.encode("utf-8")) + return builder.finalize() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py new file mode 100644 index 0000000000..8402f6095d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py @@ -0,0 +1,32 @@ +# The Timestamp type. +Timestamp = datetime.datetime + +# There is a loss of precision when converting from Rust timestamps, +# which are accurate to the nanosecond, +# to Python datetimes, which have a variable precision due to the use of float as representation. +class _UniffiConverterTimestamp(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + seconds = buf.read_i64() + microseconds = buf.read_u32() / 1000 + # Use fromtimestamp(0) then add the seconds using a timedelta. This + # ensures that we get OverflowError rather than ValueError when + # seconds is too large. + if seconds >= 0: + return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) + datetime.timedelta(seconds=seconds, microseconds=microseconds) + else: + return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds) + + @staticmethod + def write(value, buf): + if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc): + sign = 1 + delta = value - datetime.datetime.fromtimestamp(0, datetime.timezone.utc) + else: + sign = -1 + delta = datetime.datetime.fromtimestamp(0, datetime.timezone.utc) - value + + seconds = delta.seconds + delta.days * 24 * 3600 + nanoseconds = delta.microseconds * 1000 + buf.write_i64(sign * seconds) + buf.write_u32(nanoseconds) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py new file mode 100644 index 0000000000..f258b60a1c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -0,0 +1,38 @@ +{%- if func.is_async() %} + +def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): + return _uniffi_rust_call_async( + _UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}), + _UniffiLib.{{func.ffi_rust_future_poll(ci) }}, + _UniffiLib.{{func.ffi_rust_future_complete(ci) }}, + _UniffiLib.{{func.ffi_rust_future_free(ci) }}, + # lift function + {%- match func.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lift_fn }}, + {%- when None %} + lambda val: None, + {% endmatch %} + # Error FFI converter + {%- match func.throws_type() %} + {%- when Some(e) %} + {{ e|ffi_converter_name }}, + {%- when None %} + None, + {%- endmatch %} + ) + +{%- else %} +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": + {%- call py::setup_args(func) %} + return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %}) +{% when None %} + +def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): + {%- call py::setup_args(func) %} + {% call py::to_ffi_call(func) %} +{% endmatch %} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py new file mode 100644 index 0000000000..5e05314c37 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py @@ -0,0 +1,102 @@ +{%- import "macros.py" as py %} + +{%- for type_ in ci.iter_types() %} +{%- let type_name = type_|type_name %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} + +{# + # Map `Type` instances to an include statement for that type. + # + # There is a companion match in `PythonCodeOracle::create_code_type()` which performs a similar function for the + # Rust code. + # + # - When adding additional types here, make sure to also add a match arm to that function. + # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + #} +{%- match type_ %} + +{%- when Type::Boolean %} +{%- include "BooleanHelper.py" %} + +{%- when Type::Int8 %} +{%- include "Int8Helper.py" %} + +{%- when Type::Int16 %} +{%- include "Int16Helper.py" %} + +{%- when Type::Int32 %} +{%- include "Int32Helper.py" %} + +{%- when Type::Int64 %} +{%- include "Int64Helper.py" %} + +{%- when Type::UInt8 %} +{%- include "UInt8Helper.py" %} + +{%- when Type::UInt16 %} +{%- include "UInt16Helper.py" %} + +{%- when Type::UInt32 %} +{%- include "UInt32Helper.py" %} + +{%- when Type::UInt64 %} +{%- include "UInt64Helper.py" %} + +{%- when Type::Float32 %} +{%- include "Float32Helper.py" %} + +{%- when Type::Float64 %} +{%- include "Float64Helper.py" %} + +{%- when Type::String %} +{%- include "StringHelper.py" %} + +{%- when Type::Bytes %} +{%- include "BytesHelper.py" %} + +{%- when Type::Enum { name, module_path } %} +{%- let e = ci.get_enum_definition(name).unwrap() %} +{# For enums, there are either an error *or* an enum, they can't be both. #} +{%- if ci.is_name_used_as_error(name) %} +{%- include "ErrorTemplate.py" %} +{%- else %} +{%- include "EnumTemplate.py" %} +{% endif %} + +{%- when Type::Record { name, module_path } %} +{%- include "RecordTemplate.py" %} + +{%- when Type::Object { name, module_path, imp } %} +{%- include "ObjectTemplate.py" %} + +{%- when Type::Timestamp %} +{%- include "TimestampHelper.py" %} + +{%- when Type::Duration %} +{%- include "DurationHelper.py" %} + +{%- when Type::Optional { inner_type } %} +{%- include "OptionalTemplate.py" %} + +{%- when Type::Sequence { inner_type } %} +{%- include "SequenceTemplate.py" %} + +{%- when Type::Map { key_type, value_type } %} +{%- include "MapTemplate.py" %} + +{%- when Type::CallbackInterface { name: id, module_path } %} +{%- include "CallbackInterfaceTemplate.py" %} + +{%- when Type::Custom { name, module_path, builtin } %} +{%- include "CustomType.py" %} + +{%- when Type::External { name, module_path, namespace, kind, tagged } %} +{%- include "ExternalTemplate.py" %} + +{%- when Type::ForeignExecutor %} +{%- include "ForeignExecutorTemplate.py" %} + +{%- else %} +{%- endmatch %} +{%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py new file mode 100644 index 0000000000..081c6731ce --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u16" + VALUE_MIN = 0 + VALUE_MAX = 2**16 + + @staticmethod + def read(buf): + return buf.read_u16() + + @staticmethod + def write_unchecked(value, buf): + buf.write_u16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py new file mode 100644 index 0000000000..b80e75177d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u32" + VALUE_MIN = 0 + VALUE_MAX = 2**32 + + @staticmethod + def read(buf): + return buf.read_u32() + + @staticmethod + def write_unchecked(value, buf): + buf.write_u32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py new file mode 100644 index 0000000000..4b87e58547 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterUInt64(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u64" + VALUE_MIN = 0 + VALUE_MAX = 2**64 + + @staticmethod + def read(buf): + return buf.read_u64() + + @staticmethod + def write_unchecked(value, buf): + buf.write_u64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py new file mode 100644 index 0000000000..33026706f2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py @@ -0,0 +1,12 @@ +class _UniffiConverterUInt8(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u8" + VALUE_MIN = 0 + VALUE_MAX = 2**8 + + @staticmethod + def read(buf): + return buf.read_u8() + + @staticmethod + def write_unchecked(value, buf): + buf.write_u8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py new file mode 100644 index 0000000000..ef3b1bb94d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -0,0 +1,147 @@ +{# +// Template to call into rust. Used in several places. +// Variable names in `arg_list_decl` should match up with arg lists +// passed to rust via `arg_list_lowered` +#} + +{%- macro to_ffi_call(func) -%} + {%- match func.throws_type() -%} + {%- when Some with (e) -%} +_rust_call_with_error({{ e|ffi_converter_name }}, + {%- else -%} +_rust_call( + {%- endmatch -%} + _UniffiLib.{{ func.ffi_func().name() }}, + {%- call arg_list_lowered(func) -%} +) +{%- endmacro -%} + +{%- macro to_ffi_call_with_prefix(prefix, func) -%} + {%- match func.throws_type() -%} + {%- when Some with (e) -%} +_rust_call_with_error( + {{ e|ffi_converter_name }}, + {%- else -%} +_rust_call( + {%- endmatch -%} + _UniffiLib.{{ func.ffi_func().name() }}, + {{- prefix }}, + {%- call arg_list_lowered(func) -%} +) +{%- endmacro -%} + +{%- macro arg_list_lowered(func) %} + {%- for arg in func.arguments() %} + {{ arg|lower_fn }}({{ arg.name()|var_name }}) + {%- if !loop.last %},{% endif %} + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in Python declarations of methods, functions and constructors. +// Note the var_name and type_name filters. +-#} + +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name }} + {%- match arg.default_value() %} + {%- when Some with(literal) %}: "typing.Union[object, {{ arg|type_name -}}]" = _DEFAULT + {%- else %}: "{{ arg|type_name -}}" + {%- endmatch %} + {%- if !loop.last %},{% endif -%} + {%- endfor %} +{%- endmacro %} + +{#- +// Arglist as used in the _UniffiLib function declarations. +// Note unfiltered name but ffi_type_name filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor %} + {%- if func.has_rust_call_status_arg() %} + ctypes.POINTER(_UniffiRustCallStatus),{% endif %} +{% endmacro -%} + +{# + # Setup function arguments by initializing default values. + #} +{%- macro setup_args(func) %} + {%- for arg in func.arguments() %} + {%- match arg.default_value() %} + {%- when None %} + {%- when Some with(literal) %} + if {{ arg.name()|var_name }} is _DEFAULT: + {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} + {%- endmatch %} + {% endfor -%} +{%- endmacro -%} + +{# + # Exactly the same thing as `setup_args()` but with an extra 4 spaces of + # indent so that it works with object methods. + #} +{%- macro setup_args_extra_indent(func) %} + {%- for arg in func.arguments() %} + {%- match arg.default_value() %} + {%- when None %} + {%- when Some with(literal) %} + if {{ arg.name()|var_name }} is _DEFAULT: + {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} + {%- endmatch %} + {% endfor -%} +{%- endmacro -%} + +{# + # Macro to call methods + #} +{%- macro method_decl(py_method_name, meth) %} +{% if meth.is_async() %} + + def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + {%- call setup_args_extra_indent(meth) %} + return _uniffi_rust_call_async( + _UniffiLib.{{ meth.ffi_func().name() }}( + self._pointer, {% call arg_list_lowered(meth) %} + ), + _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, + _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, + _UniffiLib.{{ meth.ffi_rust_future_free(ci) }}, + # lift function + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lift_fn }}, + {%- when None %} + lambda val: None, + {% endmatch %} + # Error FFI converter + {%- match meth.throws_type() %} + {%- when Some(e) %} + {{ e|ffi_converter_name }}, + {%- when None %} + None, + {%- endmatch %} + ) + +{%- else -%} +{%- match meth.return_type() %} + +{%- when Some with (return_type) %} + + def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": + {%- call setup_args_extra_indent(meth) %} + return {{ return_type|lift_fn }}( + {% call to_ffi_call_with_prefix("self._pointer", meth) %} + ) + +{%- when None %} + + def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + {%- call setup_args_extra_indent(meth) %} + {% call to_ffi_call_with_prefix("self._pointer", meth) %} +{% endmatch %} +{% endif %} + +{% endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py new file mode 100644 index 0000000000..24c3290ff7 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -0,0 +1,74 @@ +# This file was autogenerated by some hot garbage in the `uniffi` crate. +# Trust me, you don't want to mess with it! + +# Common helper code. +# +# Ideally this would live in a separate .py file where it can be unittested etc +# in isolation, and perhaps even published as a re-useable package. +# +# However, it's important that the details of how this helper code works (e.g. the +# way that different builtin types are passed across the FFI) exactly match what's +# expected by the rust code on the other side of the interface. In practice right +# now that means coming from the exact some version of `uniffi` that was used to +# compile the rust component. The easiest way to ensure this is to bundle the Python +# helpers directly inline like we're doing here. + +import os +import sys +import ctypes +import enum +import struct +import contextlib +import datetime +import typing +{%- if ci.has_async_fns() %} +import asyncio +{%- endif %} +import platform +{%- for req in self.imports() %} +{{ req.render() }} +{%- endfor %} + +# Used for default argument values +_DEFAULT = object() + +{% include "RustBufferTemplate.py" %} +{% include "Helpers.py" %} +{% include "PointerManager.py" %} +{% include "RustBufferHelper.py" %} + +# Contains loading, initialization code, and the FFI Function declarations. +{% include "NamespaceLibraryTemplate.py" %} + +# Async support +{%- if ci.has_async_fns() %} +{%- include "Async.py" %} +{%- endif %} + +# Public interface members begin here. +{{ type_helper_code }} + +{%- for func in ci.function_definitions() %} +{%- include "TopLevelFunctionTemplate.py" %} +{%- endfor %} + +__all__ = [ + "InternalError", + {%- for e in ci.enum_definitions() %} + "{{ e|type_name }}", + {%- endfor %} + {%- for record in ci.record_definitions() %} + "{{ record|type_name }}", + {%- endfor %} + {%- for func in ci.function_definitions() %} + "{{ func.name()|fn_name }}", + {%- endfor %} + {%- for obj in ci.object_definitions() %} + "{{ obj|type_name }}", + {%- endfor %} + {%- for c in ci.callback_interface_definitions() %} + "{{ c.name()|class_name }}", + {%- endfor %} +] + +{% import "macros.py" as py %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs new file mode 100644 index 0000000000..0fcf09996f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs @@ -0,0 +1,69 @@ +/* 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/. */ + +use crate::{ + bindings::{RunScriptOptions, TargetLanguage}, + library_mode::generate_bindings, +}; +use anyhow::{Context, Result}; +use camino::Utf8Path; +use std::env; +use std::ffi::OsString; +use std::process::Command; +use uniffi_testing::UniFFITestHelper; + +/// Run Python tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + run_script( + tmp_dir, + fixture_name, + script_file, + vec![], + &RunScriptOptions::default(), + ) +} + +/// Run a Python script +/// +/// This function will set things up so that the script can import the UniFFI bindings for a crate +pub fn run_script( + tmp_dir: &str, + crate_name: &str, + script_file: &str, + args: Vec<String>, + _options: &RunScriptOptions, +) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let test_helper = UniFFITestHelper::new(crate_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + generate_bindings( + &cdylib_path, + None, + &[TargetLanguage::Python], + &out_dir, + false, + )?; + + let pythonpath = env::var_os("PYTHONPATH").unwrap_or_else(|| OsString::from("")); + let pythonpath = env::join_paths( + env::split_paths(&pythonpath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]), + )?; + + let mut command = Command::new("python3"); + command + .current_dir(out_dir) + .env("PYTHONPATH", pythonpath) + .arg(script_path) + .args(args); + let status = command + .spawn() + .context("Failed to spawn `python3` when running script")? + .wait() + .context("Failed to wait for `python3` when running script")?; + if !status.success() { + anyhow::bail!("running `python3` failed"); + } + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs new file mode 100644 index 0000000000..1f1bf8e299 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -0,0 +1,375 @@ +/* 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/. */ + +use anyhow::Result; +use askama::Template; +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; +use std::collections::HashMap; + +use crate::interface::*; +use crate::BindingsConfig; + +const RESERVED_WORDS: &[&str] = &[ + "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", + "elsif", "END", "end", "ensure", "false", "for", "if", "module", "next", "nil", "not", "or", + "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", + "until", "when", "while", "yield", "__FILE__", "__LINE__", +]; + +fn is_reserved_word(word: &str) -> bool { + RESERVED_WORDS.contains(&word) +} + +/// Get the canonical, unique-within-this-component name for a type. +/// +/// When generating helper code for foreign language bindings, it's sometimes useful to be +/// able to name a particular type in order to e.g. call a helper function that is specific +/// to that type. We support this by defining a naming convention where each type gets a +/// unique canonical name, constructed recursively from the names of its component types (if any). +pub fn canonical_name(t: &Type) -> String { + match t { + // Builtin primitive types, with plain old names. + Type::Int8 => "i8".into(), + Type::UInt8 => "u8".into(), + Type::Int16 => "i16".into(), + Type::UInt16 => "u16".into(), + Type::Int32 => "i32".into(), + Type::UInt32 => "u32".into(), + Type::Int64 => "i64".into(), + Type::UInt64 => "u64".into(), + Type::Float32 => "f32".into(), + Type::Float64 => "f64".into(), + Type::String => "string".into(), + Type::Bytes => "bytes".into(), + Type::Boolean => "bool".into(), + // API defined types. + // Note that these all get unique names, and the parser ensures that the names do not + // conflict with a builtin type. We add a prefix to the name to guard against pathological + // cases like a record named `SequenceRecord` interfering with `sequence<Record>`. + // However, types that support importing all end up with the same prefix of "Type", so + // that the import handling code knows how to find the remote reference. + Type::Object { name, .. } => format!("Type{name}"), + Type::Enum { name, .. } => format!("Type{name}"), + Type::Record { name, .. } => format!("Type{name}"), + Type::CallbackInterface { name, .. } => format!("CallbackInterface{name}"), + Type::Timestamp => "Timestamp".into(), + Type::Duration => "Duration".into(), + Type::ForeignExecutor => "ForeignExecutor".into(), + // Recursive types. + // These add a prefix to the name of the underlying type. + // The component API definition cannot give names to recursive types, so as long as the + // prefixes we add here are all unique amongst themselves, then we have no chance of + // acccidentally generating name collisions. + Type::Optional { inner_type } => format!("Optional{}", canonical_name(inner_type)), + Type::Sequence { inner_type } => format!("Sequence{}", canonical_name(inner_type)), + Type::Map { + key_type, + value_type, + } => format!( + "Map{}{}", + canonical_name(key_type).to_upper_camel_case(), + canonical_name(value_type).to_upper_camel_case() + ), + // A type that exists externally. + Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"), + } +} + +// Some config options for it the caller wants to customize the generated ruby. +// Note that this can only be used to control details of the ruby *that do not affect the underlying component*, +// since the details of the underlying component are entirely determined by the `ComponentInterface`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + cdylib_name: Option<String>, + cdylib_path: Option<String>, +} + +impl Config { + pub fn cdylib_name(&self) -> String { + self.cdylib_name + .clone() + .unwrap_or_else(|| "uniffi".to_string()) + } + + pub fn custom_cdylib_path(&self) -> bool { + self.cdylib_path.is_some() + } + + pub fn cdylib_path(&self) -> String { + self.cdylib_path.clone().unwrap_or_default() + } +} + +impl BindingsConfig for Config { + fn update_from_ci(&mut self, ci: &ComponentInterface) { + self.cdylib_name + .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); + } + + fn update_from_cdylib_name(&mut self, cdylib_name: &str) { + self.cdylib_name + .get_or_insert_with(|| cdylib_name.to_string()); + } + + fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} +} + +#[derive(Template)] +#[template(syntax = "rb", escape = "none", path = "wrapper.rb")] +pub struct RubyWrapper<'a> { + config: Config, + ci: &'a ComponentInterface, + canonical_name: &'a dyn Fn(&Type) -> String, +} +impl<'a> RubyWrapper<'a> { + pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { + Self { + config, + ci, + canonical_name: &canonical_name, + } + } +} + +mod filters { + use super::*; + pub use crate::backend::filters::*; + + pub fn type_ffi(type_: &FfiType) -> Result<String, askama::Error> { + Ok(match type_ { + FfiType::Int8 => ":int8".to_string(), + FfiType::UInt8 => ":uint8".to_string(), + FfiType::Int16 => ":int16".to_string(), + FfiType::UInt16 => ":uint16".to_string(), + FfiType::Int32 => ":int32".to_string(), + FfiType::UInt32 => ":uint32".to_string(), + FfiType::Int64 => ":int64".to_string(), + FfiType::UInt64 => ":uint64".to_string(), + FfiType::Float32 => ":float".to_string(), + FfiType::Float64 => ":double".to_string(), + FfiType::RustArcPtr(_) => ":pointer".to_string(), + FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), + FfiType::ForeignBytes => "ForeignBytes".to_string(), + FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"), + FfiType::ForeignExecutorCallback => { + unimplemented!("Foreign executors are not implemented") + } + FfiType::ForeignExecutorHandle => { + unimplemented!("Foreign executors are not implemented") + } + FfiType::RustFutureHandle + | FfiType::RustFutureContinuationCallback + | FfiType::RustFutureContinuationData => { + unimplemented!("Async functions are not implemented") + } + }) + } + + pub fn literal_rb(literal: &Literal) -> Result<String, askama::Error> { + Ok(match literal { + Literal::Boolean(v) => { + if *v { + "true".into() + } else { + "false".into() + } + } + // use the double-quote form to match with the other languages, and quote escapes. + Literal::String(s) => format!("\"{s}\""), + Literal::Null => "nil".into(), + Literal::EmptySequence => "[]".into(), + Literal::EmptyMap => "{}".into(), + Literal::Enum(v, type_) => match type_ { + Type::Enum { name, .. } => { + format!("{}::{}", class_name_rb(name)?, enum_name_rb(v)?) + } + _ => panic!("Unexpected type in enum literal: {type_:?}"), + }, + // https://docs.ruby-lang.org/en/2.0.0/syntax/literals_rdoc.html + Literal::Int(i, radix, _) => match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + Literal::UInt(i, radix, _) => match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + Literal::Float(string, _type_) => string.clone(), + }) + } + + pub fn class_name_rb(nm: &str) -> Result<String, askama::Error> { + Ok(nm.to_string().to_upper_camel_case()) + } + + pub fn fn_name_rb(nm: &str) -> Result<String, askama::Error> { + Ok(nm.to_string().to_snake_case()) + } + + pub fn var_name_rb(nm: &str) -> Result<String, askama::Error> { + let nm = nm.to_string(); + let prefix = if is_reserved_word(&nm) { "_" } else { "" }; + + Ok(format!("{prefix}{}", nm.to_snake_case())) + } + + pub fn enum_name_rb(nm: &str) -> Result<String, askama::Error> { + Ok(nm.to_string().to_shouty_snake_case()) + } + + pub fn coerce_rb(nm: &str, ns: &str, type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Int8 => format!("{ns}::uniffi_in_range({nm}, \"i8\", -2**7, 2**7)"), + Type::Int16 => format!("{ns}::uniffi_in_range({nm}, \"i16\", -2**15, 2**15)"), + Type::Int32 => format!("{ns}::uniffi_in_range({nm}, \"i32\", -2**31, 2**31)"), + Type::Int64 => format!("{ns}::uniffi_in_range({nm}, \"i64\", -2**63, 2**63)"), + Type::UInt8 => format!("{ns}::uniffi_in_range({nm}, \"u8\", 0, 2**8)"), + Type::UInt16 => format!("{ns}::uniffi_in_range({nm}, \"u16\", 0, 2**16)"), + Type::UInt32 => format!("{ns}::uniffi_in_range({nm}, \"u32\", 0, 2**32)"), + Type::UInt64 => format!("{ns}::uniffi_in_range({nm}, \"u64\", 0, 2**64)"), + Type::Float32 | Type::Float64 => nm.to_string(), + Type::Boolean => format!("{nm} ? true : false"), + Type::Object { .. } | Type::Enum { .. } | Type::Record { .. } => nm.to_string(), + Type::String => format!("{ns}::uniffi_utf8({nm})"), + Type::Bytes => format!("{ns}::uniffi_bytes({nm})"), + Type::Timestamp | Type::Duration => nm.to_string(), + Type::CallbackInterface { .. } => { + panic!("No support for coercing callback interfaces yet") + } + Type::Optional { inner_type: t } => format!("({nm} ? {} : nil)", coerce_rb(nm, ns, t)?), + Type::Sequence { inner_type: t } => { + let coerce_code = coerce_rb("v", ns, t)?; + if coerce_code == "v" { + nm.to_string() + } else { + format!("{nm}.map {{ |v| {coerce_code} }}") + } + } + Type::Map { value_type: t, .. } => { + let k_coerce_code = coerce_rb("k", ns, &Type::String)?; + let v_coerce_code = coerce_rb("v", ns, t)?; + + if k_coerce_code == "k" && v_coerce_code == "v" { + nm.to_string() + } else { + format!( + "{nm}.each.with_object({{}}) {{ |(k, v), res| res[{k_coerce_code}] = {v_coerce_code} }}" + ) + } + } + Type::External { .. } => panic!("No support for external types, yet"), + Type::Custom { .. } => panic!("No support for custom types, yet"), + Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), + }) + } + + pub fn lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 + | Type::Float32 + | Type::Float64 => nm.to_string(), + Type::Boolean => format!("({nm} ? 1 : 0)"), + Type::String => format!("RustBuffer.allocFromString({nm})"), + Type::Bytes => format!("RustBuffer.allocFromBytes({nm})"), + Type::Object { name, .. } => format!("({}._uniffi_lower {nm})", class_name_rb(name)?), + Type::CallbackInterface { .. } => { + panic!("No support for lowering callback interfaces yet") + } + Type::Enum { .. } + | Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Timestamp + | Type::Duration + | Type::Map { .. } => format!( + "RustBuffer.alloc_from_{}({})", + class_name_rb(&canonical_name(type_))?, + nm + ), + Type::External { .. } => panic!("No support for lowering external types, yet"), + Type::Custom { .. } => panic!("No support for lowering custom types, yet"), + Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), + }) + } + + pub fn lift_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::Int32 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 => format!("{nm}.to_i"), + Type::Float32 | Type::Float64 => format!("{nm}.to_f"), + Type::Boolean => format!("1 == {nm}"), + Type::String => format!("{nm}.consumeIntoString"), + Type::Bytes => format!("{nm}.consumeIntoBytes"), + Type::Object { name, .. } => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?), + Type::CallbackInterface { .. } => { + panic!("No support for lifting callback interfaces, yet") + } + Type::Enum { .. } => { + format!( + "{}.consumeInto{}", + nm, + class_name_rb(&canonical_name(type_))? + ) + } + Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Timestamp + | Type::Duration + | Type::Map { .. } => format!( + "{}.consumeInto{}", + nm, + class_name_rb(&canonical_name(type_))? + ), + Type::External { .. } => panic!("No support for lifting external types, yet"), + Type::Custom { .. } => panic!("No support for lifting custom types, yet"), + Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), + }) + } +} + +#[cfg(test)] +mod test_type { + use super::*; + + #[test] + fn test_canonical_names() { + // Non-exhaustive, but gives a bit of a flavour of what we want. + assert_eq!(canonical_name(&Type::UInt8), "u8"); + assert_eq!(canonical_name(&Type::String), "string"); + assert_eq!(canonical_name(&Type::Bytes), "bytes"); + assert_eq!( + canonical_name(&Type::Optional { + inner_type: Box::new(Type::Sequence { + inner_type: Box::new(Type::Object { + module_path: "anything".to_string(), + name: "Example".into(), + imp: ObjectImpl::Struct, + }) + }) + }), + "OptionalSequenceTypeExample" + ); + } +} + +#[cfg(test)] +mod tests; diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs new file mode 100644 index 0000000000..9ae5d1816f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/tests.rs @@ -0,0 +1,47 @@ +use super::{is_reserved_word, Config}; + +#[test] +fn when_reserved_word() { + assert!(is_reserved_word("end")); +} + +#[test] +fn when_not_reserved_word() { + assert!(!is_reserved_word("ruby")); +} + +#[test] +fn cdylib_name() { + let config = Config { + cdylib_name: None, + cdylib_path: None, + }; + + assert_eq!("uniffi", config.cdylib_name()); + + let config = Config { + cdylib_name: Some("todolist".to_string()), + cdylib_path: None, + }; + + assert_eq!("todolist", config.cdylib_name()); +} + +#[test] +fn cdylib_path() { + let config = Config { + cdylib_name: None, + cdylib_path: None, + }; + + assert_eq!("", config.cdylib_path()); + assert!(!config.custom_cdylib_path()); + + let config = Config { + cdylib_name: None, + cdylib_path: Some("/foo/bar".to_string()), + }; + + assert_eq!("/foo/bar", config.cdylib_path()); + assert!(config.custom_cdylib_path()); +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs new file mode 100644 index 0000000000..6db89b9f0b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/mod.rs @@ -0,0 +1,48 @@ +/* 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/. */ + +use std::process::Command; + +use anyhow::{Context, Result}; +use camino::Utf8Path; +use fs_err as fs; + +pub mod gen_ruby; +mod test; +pub use gen_ruby::{Config, RubyWrapper}; +pub use test::{run_test, test_script_command}; + +use super::super::interface::ComponentInterface; + +// Generate ruby bindings for the given ComponentInterface, in the given output directory. + +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<()> { + let rb_file = out_dir.join(format!("{}.rb", ci.namespace())); + fs::write(&rb_file, generate_ruby_bindings(config, ci)?)?; + + if try_format_code { + if let Err(e) = Command::new("rubocop").arg("-A").arg(&rb_file).output() { + println!( + "Warning: Unable to auto-format {} using rubocop: {e:?}", + rb_file.file_name().unwrap(), + ) + } + } + + Ok(()) +} + +// Generate ruby bindings for the given ComponentInterface, as a string. + +pub fn generate_ruby_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> { + use askama::Template; + RubyWrapper::new(config.clone(), ci) + .render() + .context("failed to render ruby bindings") +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb new file mode 100644 index 0000000000..23b701f6a7 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/EnumTemplate.rb @@ -0,0 +1,59 @@ +{% if e.is_flat() %} + +class {{ e.name()|class_name_rb }} + {% for variant in e.variants() -%} + {{ variant.name()|enum_name_rb }} = {{ loop.index }} + {% endfor %} +end + +{% else %} + +class {{ e.name()|class_name_rb }} + def initialize + raise RuntimeError, '{{ e.name()|class_name_rb }} cannot be instantiated directly' + end + + # Each enum variant is a nested class of the enum itself. + {% for variant in e.variants() -%} + class {{ variant.name()|enum_name_rb }} + {% if variant.has_fields() %} + attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %} + {% endif %} + def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + {% if variant.has_fields() %} + {%- for field in variant.fields() %} + @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }} + {%- endfor %} + {% else %} + {% endif %} + end + + def to_s + "{{ e.name()|class_name_rb }}::{{ variant.name()|enum_name_rb }}({% for field in variant.fields() %}{{ field.name() }}=#{@{{ field.name() }}}{% if loop.last %}{% else %}, {% endif %}{% endfor %})" + end + + def ==(other) + if !other.{{ variant.name()|var_name_rb }}? + return false + end + {%- for field in variant.fields() %} + if @{{ field.name()|var_name_rb }} != other.{{ field.name()|var_name_rb }} + return false + end + {%- endfor %} + + true + end + + # For each variant, we have an `NAME?` method for easily checking + # whether an instance is that variant. + {% for variant in e.variants() %} + def {{ variant.name()|var_name_rb }}? + instance_of? {{ e.name()|class_name_rb }}::{{ variant.name()|enum_name_rb }} + end + {% endfor %} + end + {% endfor %} +end + +{% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb new file mode 100644 index 0000000000..a7e26370c8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb @@ -0,0 +1,121 @@ +class RustCallStatus < FFI::Struct + layout :code, :int8, + :error_buf, RustBuffer + + def code + self[:code] + end + + def error_buf + self[:error_buf] + end + + def to_s + "RustCallStatus(code=#{self[:code]})" + end +end + +# These match the values from the uniffi::rustcalls module +CALL_SUCCESS = 0 +CALL_ERROR = 1 +CALL_PANIC = 2 +{%- for e in ci.enum_definitions() %} +{% if ci.is_name_used_as_error(e.name()) %} +{% if e.is_flat() %} +class {{ e.name()|class_name_rb }} + {%- for variant in e.variants() %} + {{ variant.name()|class_name_rb }} = Class.new StandardError + {%- endfor %} +{% else %} +module {{ e.name()|class_name_rb }} + {%- for variant in e.variants() %} + class {{ variant.name()|class_name_rb }} < StandardError + def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %}) + {%- for field in variant.fields() %} + @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }} + {%- endfor %} + super() + end + {%- if variant.has_fields() %} + + attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %} + {% endif %} + + def to_s + "#{self.class.name}({% for field in variant.fields() %}{{ field.name()|var_name_rb }}=#{@{{ field.name()|var_name_rb }}.inspect}{% if !loop.last %}, {% endif %}{% endfor %})" + end + end + {%- endfor %} +{% endif %} +end +{% endif %} +{%- endfor %} + +# Map error modules to the RustBuffer method name that reads them +ERROR_MODULE_TO_READER_METHOD = { +{%- for e in ci.enum_definitions() %} +{% if ci.is_name_used_as_error(e.name()) %} +{%- let typ=ci.get_type(e.name()).unwrap() %} +{%- let canonical_type_name = canonical_name(typ.borrow()).borrow()|class_name_rb %} + {{ e.name()|class_name_rb }} => :read{{ canonical_type_name }}, +{% endif %} +{%- endfor %} +} + +private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC, + :RustCallStatus + +def self.consume_buffer_into_error(error_module, rust_buffer) + rust_buffer.consumeWithStream do |stream| + reader_method = ERROR_MODULE_TO_READER_METHOD[error_module] + return stream.send(reader_method) + end +end + +class InternalError < StandardError +end + +def self.rust_call(fn_name, *args) + # Call a rust function + rust_call_with_error(nil, fn_name, *args) +end + +def self.rust_call_with_error(error_module, fn_name, *args) + # Call a rust function and handle errors + # + # Use this when the rust function returns a Result<>. error_module must be the error_module that corresponds to that Result. + + + # Note: RustCallStatus.new zeroes out the struct, which is exactly what we + # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0, + # data=NULL)) + status = RustCallStatus.new + args << status + + result = UniFFILib.public_send(fn_name, *args) + + case status.code + when CALL_SUCCESS + result + when CALL_ERROR + if error_module.nil? + status.error_buf.free + raise InternalError, "CALL_ERROR with no error_module set" + else + raise consume_buffer_into_error(error_module, status.error_buf) + end + when CALL_PANIC + # When the rust code sees a panic, it tries to construct a RustBuffer + # with the message. But if that code panics, then it just sends back + # an empty buffer. + if status.error_buf.len > 0 + raise InternalError, status.error_buf.consumeIntoString() + else + raise InternalError, "Rust panic" + end + else + raise InternalError, "Unknown call status: #{status.code}" + end +end + +private_class_method :consume_buffer_into_error diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb new file mode 100644 index 0000000000..49fa247a77 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb @@ -0,0 +1,18 @@ +def self.uniffi_in_range(i, type_name, min, max) + raise TypeError, "no implicit conversion of #{i} into Integer" unless i.respond_to?(:to_int) + i = i.to_int + raise RangeError, "#{type_name} requires #{min} <= value < #{max}" unless (min <= i && i < max) + i +end + +def self.uniffi_utf8(v) + raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str) + v = v.to_str.encode(Encoding::UTF_8) + raise Encoding::InvalidByteSequenceError, "not a valid UTF-8 encoded string" unless v.valid_encoding? + v +end + +def self.uniffi_bytes(v) + raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str) + v.to_str +end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb new file mode 100644 index 0000000000..8536fc322e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/NamespaceLibraryTemplate.rb @@ -0,0 +1,17 @@ +# This is how we find and load the dynamic library provided by the component. +# For now we just look it up by name. +module UniFFILib + extend FFI::Library + + {% if config.custom_cdylib_path() %} + ffi_lib {{ config.cdylib_path() }} + {% else %} + ffi_lib '{{ config.cdylib_name() }}' + {% endif %} + + {% for func in ci.iter_ffi_function_definitions_non_async() -%} + attach_function :{{ func.name() }}, + {%- call rb::arg_list_ffi_decl(func) %}, + {% match func.return_type() %}{% when Some with (type_) %}{{ type_|type_ffi }}{% when None %}:void{% endmatch %} + {% endfor %} +end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb new file mode 100644 index 0000000000..677c5c729b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb @@ -0,0 +1,73 @@ +class {{ obj.name()|class_name_rb }} + + # A private helper for initializing instances of the class from a raw pointer, + # bypassing any initialization logic and ensuring they are GC'd properly. + def self._uniffi_allocate(pointer) + pointer.autorelease = false + inst = allocate + inst.instance_variable_set :@pointer, pointer + ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) + return inst + end + + # A private helper for registering an object finalizer. + # N.B. it's important that this does not capture a reference + # to the actual instance, only its underlying pointer. + def self._uniffi_define_finalizer_by_pointer(pointer, object_id) + Proc.new do |_id| + {{ ci.namespace()|class_name_rb }}.rust_call( + :{{ obj.ffi_object_free().name() }}, + pointer + ) + end + end + + # A private helper for lowering instances into a raw pointer. + # This does an explicit typecheck, because accidentally lowering a different type of + # object in a place where this type is expected, could lead to memory unsafety. + def self._uniffi_lower(inst) + if not inst.is_a? self + raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}" + end + return inst.instance_variable_get :@pointer + end + + {%- match obj.primary_constructor() %} + {%- when Some with (cons) %} + def initialize({% call rb::arg_list_decl(cons) -%}) + {%- call rb::coerce_args_extra_indent(cons) %} + pointer = {% call rb::to_ffi_call(cons) %} + @pointer = pointer + ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id)) + end + {%- when None %} + {%- endmatch %} + + {% for cons in obj.alternate_constructors() -%} + def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) + {%- call rb::coerce_args_extra_indent(cons) %} + # Call the (fallible) function before creating any half-baked object instances. + # Lightly yucky way to bypass the usual "initialize" logic + # and just create a new instance with the required pointer. + return _uniffi_allocate({% call rb::to_ffi_call(cons) %}) + end + {% endfor %} + + {% for meth in obj.methods() -%} + {%- match meth.return_type() -%} + + {%- when Some with (return_type) -%} + def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) + {%- call rb::coerce_args_extra_indent(meth) %} + result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + return {{ "result"|lift_rb(return_type) }} + end + + {%- when None -%} + def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) + {%- call rb::coerce_args_extra_indent(meth) %} + {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + end + {% endmatch %} + {% endfor %} +end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb new file mode 100644 index 0000000000..c940b31060 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb @@ -0,0 +1,20 @@ +# Record type {{ rec.name() }} +class {{ rec.name()|class_name_rb }} + attr_reader {% for field in rec.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %} + + def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + {%- for field in rec.fields() %} + @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }} + {%- endfor %} + end + + def ==(other) + {%- for field in rec.fields() %} + if @{{ field.name()|var_name_rb }} != other.{{ field.name()|var_name_rb }} + return false + end + {%- endfor %} + + true + end +end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb new file mode 100644 index 0000000000..8749139116 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -0,0 +1,264 @@ + +# Helper for structured writing of values into a RustBuffer. +class RustBufferBuilder + def initialize + @rust_buf = RustBuffer.alloc 16 + @rust_buf.len = 0 + end + + def finalize + rbuf = @rust_buf + + @rust_buf = nil + + rbuf + end + + def discard + return if @rust_buf.nil? + + rbuf = finalize + rbuf.free + end + + def write(value) + reserve(value.bytes.size) do + @rust_buf.data.put_array_of_char @rust_buf.len, value.bytes + end + end + + {% for typ in ci.iter_types() -%} + {%- let canonical_type_name = canonical_name(typ).borrow()|class_name_rb -%} + {%- match typ -%} + + {% when Type::Int8 -%} + + def write_I8(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "i8", -2**7, 2**7) + pack_into(1, 'c', v) + end + + {% when Type::UInt8 -%} + + def write_U8(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "u8", 0, 2**8) + pack_into(1, 'c', v) + end + + {% when Type::Int16 -%} + + def write_I16(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "i16", -2**15, 2**15) + pack_into(2, 's>', v) + end + + {% when Type::UInt16 -%} + + def write_U16(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "u16", 0, 2**16) + pack_into(2, 'S>', v) + end + + {% when Type::Int32 -%} + + def write_I32(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "i32", -2**31, 2**31) + pack_into(4, 'l>', v) + end + + {% when Type::UInt32 -%} + + def write_U32(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "u32", 0, 2**32) + pack_into(4, 'L>', v) + end + + {% when Type::Int64 -%} + + def write_I64(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "i64", -2**63, 2**63) + pack_into(8, 'q>', v) + end + + {% when Type::UInt64 -%} + + def write_U64(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_in_range(v, "u64", 0, 2**64) + pack_into(8, 'Q>', v) + end + + {% when Type::Float32 -%} + + def write_F32(v) + pack_into(4, 'g', v) + end + + {% when Type::Float64 -%} + + def write_F64(v) + pack_into(8, 'G', v) + end + + {% when Type::Boolean -%} + + def write_Bool(v) + pack_into(1, 'c', v ? 1 : 0) + end + + {% when Type::String -%} + + def write_String(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_utf8(v) + pack_into 4, 'l>', v.bytes.size + write v + end + + {% when Type::Bytes -%} + + def write_Bytes(v) + v = {{ ci.namespace()|class_name_rb }}::uniffi_bytes(v) + pack_into 4, 'l>', v.bytes.size + write v + end + + {% when Type::Timestamp -%} + # The Timestamp type. + ONE_SECOND_IN_NANOSECONDS = 10**9 + + def write_{{ canonical_type_name }}(v) + seconds = v.tv_sec + nanoseconds = v.tv_nsec + + # UniFFi conventions assume that nanoseconds part has to represent nanoseconds portion of + # duration between epoch and the timestamp moment. Ruby `Time#tv_nsec` returns the number of + # nanoseconds for the subsecond part, which is sort of opposite to "duration" meaning. + # Hence we need to convert value returned by `Time#tv_nsec` back and forth with the following + # logic: + if seconds < 0 && nanoseconds != 0 + # In order to get duration nsec we shift by 1 second: + nanoseconds = ONE_SECOND_IN_NANOSECONDS - nanoseconds + + # Then we compensate 1 second shift: + seconds += 1 + end + + pack_into 8, 'q>', seconds + pack_into 4, 'L>', nanoseconds + end + + {% when Type::Duration -%} + # The Duration type. + + def write_{{ canonical_type_name }}(v) + seconds = v.tv_sec + nanoseconds = v.tv_nsec + + raise ArgumentError, 'Invalid duration, must be non-negative' if seconds < 0 + + pack_into 8, 'Q>', seconds + pack_into 4, 'L>', nanoseconds + end + + {% when Type::Object with { name: object_name, module_path, imp } -%} + # The Object type {{ object_name }}. + + def write_{{ canonical_type_name }}(obj) + pointer = {{ object_name|class_name_rb}}._uniffi_lower obj + pack_into(8, 'Q>', pointer.address) + end + + {% when Type::Enum { name: enum_name, module_path } -%} + {% if !ci.is_name_used_as_error(enum_name) %} + {%- let e = ci|get_enum_definition(enum_name) -%} + # The Enum type {{ enum_name }}. + + def write_{{ canonical_type_name }}(v) + {%- if e.is_flat() %} + pack_into(4, 'l>', v) + {%- else -%} + {%- for variant in e.variants() %} + if v.{{ variant.name()|var_name_rb }}? + pack_into(4, 'l>', {{ loop.index }}) + {%- for field in variant.fields() %} + self.write_{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}(v.{{ field.name() }}) + {%- endfor %} + end + {%- endfor %} + {%- endif %} + end + {% endif %} + + {% when Type::Record { name: record_name, module_path } -%} + {%- let rec = ci|get_record_definition(record_name) -%} + # The Record type {{ record_name }}. + + def write_{{ canonical_type_name }}(v) + {%- for field in rec.fields() %} + self.write_{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}(v.{{ field.name()|var_name_rb }}) + {%- endfor %} + end + + {% when Type::Optional { inner_type } -%} + # The Optional<T> type for {{ canonical_name(inner_type) }}. + + def write_{{ canonical_type_name }}(v) + if v.nil? + pack_into(1, 'c', 0) + else + pack_into(1, 'c', 1) + self.write_{{ canonical_name(inner_type).borrow()|class_name_rb }}(v) + end + end + + {% when Type::Sequence { inner_type } -%} + # The Sequence<T> type for {{ canonical_name(inner_type) }}. + + def write_{{ canonical_type_name }}(items) + pack_into(4, 'l>', items.size) + + items.each do |item| + self.write_{{ canonical_name(inner_type).borrow()|class_name_rb }}(item) + end + end + + {% when Type::Map { key_type: k, value_type: inner_type } -%} + # The Map<T> type for {{ canonical_name(inner_type) }}. + + def write_{{ canonical_type_name }}(items) + pack_into(4, 'l>', items.size) + + items.each do |k, v| + write_String(k) + self.write_{{ canonical_name(inner_type).borrow()|class_name_rb }}(v) + end + end + + {%- else -%} + # This type is not yet supported in the Ruby backend. + def write_{{ canonical_type_name }}(v) + raise InternalError('RustBufferStream.write() not implemented yet for {{ canonical_type_name }}') + end + + {%- endmatch -%} + {%- endfor %} + + private + + def reserve(num_bytes) + if @rust_buf.len + num_bytes > @rust_buf.capacity + @rust_buf = RustBuffer.reserve(@rust_buf, num_bytes) + end + + yield + + @rust_buf.len += num_bytes + end + + def pack_into(size, format, value) + reserve(size) do + @rust_buf.data.put_array_of_char @rust_buf.len, [value].pack(format).bytes + end + end +end + +private_constant :RustBufferBuilder diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb new file mode 100644 index 0000000000..b085dddf15 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb @@ -0,0 +1,315 @@ + +# Helper for structured reading of values from a RustBuffer. +class RustBufferStream + + def initialize(rbuf) + @rbuf = rbuf + @offset = 0 + end + + def remaining + @rbuf.len - @offset + end + + def read(size) + raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len + + data = @rbuf.data.get_bytes @offset, size + + @offset += size + + data + end + + {% for typ in ci.iter_types() -%} + {%- let canonical_type_name = canonical_name(typ).borrow()|class_name_rb -%} + {%- match typ -%} + + {% when Type::Int8 -%} + + def readI8 + unpack_from 1, 'c' + end + + {% when Type::UInt8 -%} + + def readU8 + unpack_from 1, 'c' + end + + {% when Type::Int16 -%} + + def readI16 + unpack_from 2, 's>' + end + + {% when Type::UInt16 -%} + + def readU16 + unpack_from 2, 'S>' + end + + {% when Type::Int32 -%} + + def readI32 + unpack_from 4, 'l>' + end + + {% when Type::UInt32 -%} + + def readU32 + unpack_from 4, 'L>' + end + + {% when Type::Int64 -%} + + def readI64 + unpack_from 8, 'q>' + end + + {% when Type::UInt64 -%} + + def readU64 + unpack_from 8, 'Q>' + end + + {% when Type::Float32 -%} + + def readF32 + unpack_from 4, 'g' + end + + {% when Type::Float64 -%} + + def readF64 + unpack_from 8, 'G' + end + + {% when Type::Boolean -%} + + def readBool + v = unpack_from 1, 'c' + + return false if v == 0 + return true if v == 1 + + raise InternalError, 'Unexpected byte for Boolean type' + end + + {% when Type::String -%} + + def readString + size = unpack_from 4, 'l>' + + raise InternalError, 'Unexpected negative string length' if size.negative? + + read(size).force_encoding(Encoding::UTF_8) + end + + {% when Type::Bytes -%} + + def readBytes + size = unpack_from 4, 'l>' + + raise InternalError, 'Unexpected negative byte string length' if size.negative? + + read(size).force_encoding(Encoding::BINARY) + end + + {% when Type::Timestamp -%} + # The Timestamp type. + ONE_SECOND_IN_NANOSECONDS = 10**9 + + def read{{ canonical_type_name }} + seconds = unpack_from 8, 'q>' + nanoseconds = unpack_from 4, 'L>' + + # UniFFi conventions assume that nanoseconds part has to represent nanoseconds portion of + # duration between epoch and the timestamp moment. Ruby `Time#tv_nsec` returns the number of + # nanoseconds for the subsecond part, which is sort of opposite to "duration" meaning. + # Hence we need to convert value returned by `Time#tv_nsec` back and forth with the following + # logic: + if seconds < 0 && nanoseconds != 0 + # In order to get duration nsec we shift by 1 second: + nanoseconds = ONE_SECOND_IN_NANOSECONDS - nanoseconds + + # Then we compensate 1 second shift: + seconds -= 1 + end + + Time.at(seconds, nanoseconds, :nanosecond, in: '+00:00').utc + end + + {% when Type::Duration -%} + # The Duration type. + + def read{{ canonical_type_name }} + seconds = unpack_from 8, 'q>' + nanoseconds = unpack_from 4, 'L>' + + Time.at(seconds, nanoseconds, :nanosecond, in: '+00:00').utc + end + + {% when Type::Object with { name: object_name, module_path, imp } -%} + # The Object type {{ object_name }}. + + def read{{ canonical_type_name }} + pointer = FFI::Pointer.new unpack_from 8, 'Q>' + return {{ object_name|class_name_rb }}._uniffi_allocate(pointer) + end + + {% when Type::Enum { name, module_path } -%} + {%- let e = ci|get_enum_definition(name) -%} + {% if !ci.is_name_used_as_error(name) %} + {% let enum_name = name %} + # The Enum type {{ enum_name }}. + + def read{{ canonical_type_name }} + variant = unpack_from 4, 'l>' + {% if e.is_flat() -%} + {%- for variant in e.variants() %} + if variant == {{ loop.index }} + return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }} + end + {%- endfor %} + + raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}' + {%- else -%} + {%- for variant in e.variants() %} + if variant == {{ loop.index }} + {%- if variant.has_fields() %} + return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}.new( + {%- for field in variant.fields() %} + self.read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}(){% if loop.last %}{% else %},{% endif %} + {%- endfor %} + ) + {%- else %} + return {{ enum_name|class_name_rb }}::{{ variant.name()|enum_name_rb }}.new + {% endif %} + end + {%- endfor %} + raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}' + {%- endif %} + end + + {% else %} + + {% let error_name = name %} + + # The Error type {{ error_name }} + + def read{{ canonical_type_name }} + variant = unpack_from 4, 'l>' + {% if e.is_flat() -%} + {%- for variant in e.variants() %} + if variant == {{ loop.index }} + return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new( + readString() + ) + end + {%- endfor %} + + raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}' + {%- else -%} + {%- for variant in e.variants() %} + if variant == {{ loop.index }} + {%- if variant.has_fields() %} + return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new( + {%- for field in variant.fields() %} + read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}(){% if loop.last %}{% else %},{% endif %} + {%- endfor %} + ) + {%- else %} + return {{ error_name|class_name_rb }}::{{ variant.name()|class_name_rb }}.new + {%- endif %} + end + {%- endfor %} + + raise InternalError, 'Unexpected variant tag for {{ canonical_type_name }}' + {%- endif %} + end + {% endif %} + + {% when Type::Record { name: record_name, module_path } -%} + {%- let rec = ci|get_record_definition(record_name) -%} + # The Record type {{ record_name }}. + + def read{{ canonical_type_name }} + {{ rec.name()|class_name_rb }}.new( + {%- for field in rec.fields() %} + read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %} + {%- endfor %} + ) + end + + {% when Type::Optional { inner_type } -%} + # The Optional<T> type for {{ canonical_name(inner_type) }}. + + def read{{ canonical_type_name }} + flag = unpack_from 1, 'c' + + if flag == 0 + return nil + elsif flag == 1 + return read{{ canonical_name(inner_type).borrow()|class_name_rb }} + else + raise InternalError, 'Unexpected flag byte for {{ canonical_type_name }}' + end + end + + {% when Type::Sequence { inner_type } -%} + # The Sequence<T> type for {{ canonical_name(inner_type) }}. + + def read{{ canonical_type_name }} + count = unpack_from 4, 'l>' + + raise InternalError, 'Unexpected negative sequence length' if count.negative? + + items = [] + + count.times do + items.append read{{ canonical_name(inner_type).borrow()|class_name_rb }} + end + + items + end + + {% when Type::Map { key_type: k, value_type: inner_type } -%} + # The Map<T> type for {{ canonical_name(inner_type) }}. + + def read{{ canonical_type_name }} + count = unpack_from 4, 'l>' + raise InternalError, 'Unexpected negative map size' if count.negative? + + items = {} + count.times do + key = readString + items[key] = read{{ canonical_name(inner_type).borrow()|class_name_rb }} + end + + items + end + {%- else -%} + # This type is not yet supported in the Ruby backend. + def read{{ canonical_type_name }} + raise InternalError, 'RustBufferStream.read not implemented yet for {{ canonical_type_name }}' + end + + {%- endmatch -%} + {%- endfor %} + + def unpack_from(size, format) + raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len + + value = @rbuf.data.get_bytes(@offset, size).unpack format + + @offset += size + + # TODO: verify this + raise 'more than one element!!!' if value.size > 1 + + value[0] + end +end + +private_constant :RustBufferStream diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb new file mode 100644 index 0000000000..0194c9666d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb @@ -0,0 +1,236 @@ +class RustBuffer < FFI::Struct + layout :capacity, :int32, + :len, :int32, + :data, :pointer + + def self.alloc(size) + return {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_alloc().name() }}, size) + end + + def self.reserve(rbuf, additional) + return {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional) + end + + def free + {{ ci.namespace()|class_name_rb }}.rust_call(:{{ ci.ffi_rustbuffer_free().name() }}, self) + end + + def capacity + self[:capacity] + end + + def len + self[:len] + end + + def len=(value) + self[:len] = value + end + + def data + self[:data] + end + + def to_s + "RustBuffer(capacity=#{capacity}, len=#{len}, data=#{data.read_bytes len})" + end + + # The allocated buffer will be automatically freed if an error occurs, ensuring that + # we don't accidentally leak it. + def self.allocWithBuilder + builder = RustBufferBuilder.new + + begin + yield builder + rescue => e + builder.discard + raise e + end + end + + # The RustBuffer will be freed once the context-manager exits, ensuring that we don't + # leak it even if an error occurs. + def consumeWithStream + stream = RustBufferStream.new self + + yield stream + + raise RuntimeError, 'junk data left in buffer after consuming' if stream.remaining != 0 + ensure + free + end + + {%- for typ in ci.iter_types() -%} + {%- let canonical_type_name = canonical_name(typ) -%} + {%- match typ -%} + + {% when Type::String -%} + # The primitive String type. + + def self.allocFromString(value) + RustBuffer.allocWithBuilder do |builder| + builder.write value.encode('utf-8') + return builder.finalize + end + end + + def consumeIntoString + consumeWithStream do |stream| + return stream.read(stream.remaining).force_encoding(Encoding::UTF_8) + end + end + + {% when Type::Bytes -%} + # The primitive Bytes type. + + def self.allocFromBytes(value) + RustBuffer.allocWithBuilder do |builder| + builder.write_Bytes(value) + return builder.finalize + end + end + + def consumeIntoBytes + consumeWithStream do |stream| + return stream.readBytes + end + end + + {% when Type::Timestamp -%} + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + + {% when Type::Duration -%} + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + + {% when Type::Record { name: record_name, module_path } -%} + {%- let rec = ci|get_record_definition(record_name) -%} + # The Record type {{ record_name }}. + + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + + {% when Type::Enum { name: enum_name, module_path } -%} + {% if !ci.is_name_used_as_error(enum_name) %} + {%- let e = ci|get_enum_definition(enum_name) -%} + # The Enum type {{ enum_name }}. + + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + {% endif %} + + {% when Type::Optional { inner_type } -%} + # The Optional<T> type for {{ canonical_name(inner_type) }}. + + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize() + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + + {% when Type::Sequence { inner_type } -%} + # The Sequence<T> type for {{ canonical_name(inner_type) }}. + + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize() + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + + {% when Type::Map { key_type: k, value_type: inner_type } -%} + # The Map<T> type for {{ canonical_name(inner_type) }}. + + def self.alloc_from_{{ canonical_type_name }}(v) + RustBuffer.allocWithBuilder do |builder| + builder.write_{{ canonical_type_name }}(v) + return builder.finalize + end + end + + def consumeInto{{ canonical_type_name }} + consumeWithStream do |stream| + return stream.read{{ canonical_type_name }} + end + end + + {%- else -%} + {#- No code emitted for types that don't lower into a RustBuffer -#} + {%- endmatch -%} + {%- endfor %} +end + +module UniFFILib + class ForeignBytes < FFI::Struct + layout :len, :int32, + :data, :pointer + + def len + self[:len] + end + + def data + self[:data] + end + + def to_s + "ForeignBytes(len=#{len}, data=#{data.read_bytes(len)})" + end + end +end + +private_constant :UniFFILib diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb new file mode 100644 index 0000000000..13214cf31b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb @@ -0,0 +1,16 @@ +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) + {%- call rb::coerce_args(func) %} + result = {% call rb::to_ffi_call(func) %} + return {{ "result"|lift_rb(return_type) }} +end + +{% when None %} + +def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) + {%- call rb::coerce_args(func) %} + {% call rb::to_ffi_call(func) %} +end +{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb new file mode 100644 index 0000000000..8dc3e5e613 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb @@ -0,0 +1,73 @@ +{# +// Template to call into rust. Used in several places. +// Variable names in `arg_list_decl` should match up with arg lists +// passed to rust via `_arg_list_ffi_call` (we use `var_name_rb` in `lower_rb`) +#} + +{%- macro to_ffi_call(func) -%} + {%- match func.throws_name() -%} + {%- when Some with (e) -%} + {{ ci.namespace()|class_name_rb }}.rust_call_with_error({{ e|class_name_rb }}, + {%- else -%} + {{ ci.namespace()|class_name_rb }}.rust_call( + {%- endmatch -%} + :{{ func.ffi_func().name() }}, + {%- call _arg_list_ffi_call(func) -%} +) +{%- endmacro -%} + +{%- macro to_ffi_call_with_prefix(prefix, func) -%} + {%- match func.throws_name() -%} + {%- when Some with (e) -%} + {{ ci.namespace()|class_name_rb }}.rust_call_with_error({{ e|class_name_rb }}, + {%- else -%} + {{ ci.namespace()|class_name_rb }}.rust_call( + {%- endmatch -%} + :{{ func.ffi_func().name() }}, + {{- prefix }}, + {%- call _arg_list_ffi_call(func) -%} +) +{%- endmacro -%} + +{%- macro _arg_list_ffi_call(func) %} + {%- for arg in func.arguments() %} + {{- arg.name()|lower_rb(arg.as_type().borrow()) }} + {%- if !loop.last %},{% endif %} + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in Ruby declarations of methods, functions and constructors. +// Note the var_name_rb and type_rb filters. +-#} + +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {{ arg.name()|var_name_rb }} + {%- match arg.default_value() %} + {%- when Some with(literal) %} = {{ literal|literal_rb }} + {%- else %} + {%- endmatch %} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + +{#- +// Arglist as used in the UniFFILib function declarations. +// Note unfiltered name but type_ffi filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + [{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref] +{%- endmacro -%} + +{%- macro coerce_args(func) %} + {%- for arg in func.arguments() %} + {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) -}} + {% endfor -%} +{%- endmacro -%} + +{%- macro coerce_args_extra_indent(func) %} + {%- for arg in func.arguments() %} + {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {%- endfor %} +{%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb new file mode 100644 index 0000000000..e3631b68de --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/wrapper.rb @@ -0,0 +1,52 @@ +# This file was autogenerated by some hot garbage in the `uniffi` crate. +# Trust me, you don't want to mess with it! + +# Common helper code. +# +# Ideally this would live in a separate .rb file where it can be unittested etc +# in isolation, and perhaps even published as a re-useable package. +# +# However, it's important that the details of how this helper code works (e.g. the +# way that different builtin types are passed across the FFI) exactly match what's +# expected by the rust code on the other side of the interface. In practice right +# now that means coming from the exact some version of `uniffi` that was used to +# compile the rust component. The easiest way to ensure this is to bundle the Ruby +# helpers directly inline like we're doing here. + +require 'ffi' + + +module {{ ci.namespace()|class_name_rb }} + {% include "Helpers.rb" %} + + {% include "RustBufferTemplate.rb" %} + {% include "RustBufferStream.rb" %} + {% include "RustBufferBuilder.rb" %} + + # Error definitions + {% include "ErrorTemplate.rb" %} + + {% include "NamespaceLibraryTemplate.rb" %} + + # Public interface members begin here. + + {% for e in ci.enum_definitions() %} + {% if !ci.is_name_used_as_error(e.name()) %} + {% include "EnumTemplate.rb" %} + {% endif %} + {%- endfor -%} + + {%- for rec in ci.record_definitions() %} + {% include "RecordTemplate.rb" %} + {% endfor %} + + {% for func in ci.function_definitions() %} + {% include "TopLevelFunctionTemplate.rb" %} + {% endfor %} + + {% for obj in ci.object_definitions() %} + {% include "ObjectTemplate.rb" %} + {% endfor %} +end + +{% import "macros.rb" as rb %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs new file mode 100644 index 0000000000..03da37d567 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs @@ -0,0 +1,52 @@ +/* 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/. */ + +use crate::bindings::TargetLanguage; +use crate::library_mode::generate_bindings; +use anyhow::{bail, Context, Result}; +use camino::Utf8Path; +use std::env; +use std::ffi::OsString; +use std::process::{Command, Stdio}; +use uniffi_testing::UniFFITestHelper; + +/// Run Ruby tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + let status = test_script_command(tmp_dir, fixture_name, script_file)? + .spawn() + .context("Failed to spawn `ruby` when running script")? + .wait() + .context("Failed to wait for `ruby` when running script")?; + if !status.success() { + bail!("running `ruby` failed"); + } + Ok(()) +} + +/// Create a `Command` instance that runs a test script +pub fn test_script_command( + tmp_dir: &str, + fixture_name: &str, + script_file: &str, +) -> Result<Command> { + let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let test_helper = UniFFITestHelper::new(fixture_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + generate_bindings(&cdylib_path, None, &[TargetLanguage::Ruby], &out_dir, false)?; + + let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from("")); + let rubypath = env::join_paths( + env::split_paths(&rubypath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]), + )?; + + let mut command = Command::new("ruby"); + command + .current_dir(out_dir) + .env("RUBYLIB", rubypath) + .arg(script_path) + .stderr(Stdio::inherit()) + .stdout(Stdio::inherit()); + Ok(command) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs new file mode 100644 index 0000000000..5d8b37e0af --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct CallbackInterfaceCodeType { + id: String, +} + +impl CallbackInterfaceCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for CallbackInterfaceCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("CallbackInterface{}", self.type_label()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs new file mode 100644 index 0000000000..8e6dddf3f9 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -0,0 +1,108 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::{Literal, Type}; + +#[derive(Debug)] +pub struct OptionalCodeType { + inner: Type, +} + +impl OptionalCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } +} + +impl CodeType for OptionalCodeType { + fn type_label(&self) -> String { + format!("{}?", super::SwiftCodeOracle.find(&self.inner).type_label()) + } + + fn canonical_name(&self) -> String { + format!( + "Option{}", + super::SwiftCodeOracle.find(&self.inner).canonical_name() + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::Null => "nil".into(), + _ => super::SwiftCodeOracle.find(&self.inner).literal(literal), + } + } +} + +#[derive(Debug)] +pub struct SequenceCodeType { + inner: Type, +} + +impl SequenceCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } +} + +impl CodeType for SequenceCodeType { + fn type_label(&self) -> String { + format!( + "[{}]", + super::SwiftCodeOracle.find(&self.inner).type_label() + ) + } + + fn canonical_name(&self) -> String { + format!( + "Sequence{}", + super::SwiftCodeOracle.find(&self.inner).canonical_name() + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::EmptySequence => "[]".into(), + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +pub struct MapCodeType { + key: Type, + value: Type, +} + +impl MapCodeType { + pub fn new(key: Type, value: Type) -> Self { + Self { key, value } + } +} + +impl CodeType for MapCodeType { + fn type_label(&self) -> String { + format!( + "[{}: {}]", + super::SwiftCodeOracle.find(&self.key).type_label(), + super::SwiftCodeOracle.find(&self.value).type_label() + ) + } + + fn canonical_name(&self) -> String { + format!( + "Dictionary{}{}", + super::SwiftCodeOracle.find(&self.key).canonical_name(), + super::SwiftCodeOracle.find(&self.value).canonical_name() + ) + } + + fn literal(&self, literal: &Literal) -> String { + match literal { + Literal::EmptyMap => "[:]".into(), + _ => unreachable!(), + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs new file mode 100644 index 0000000000..f4591b6eb6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/custom.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct CustomCodeType { + name: String, +} + +impl CustomCodeType { + pub fn new(name: String) -> Self { + CustomCodeType { name } + } +} + +impl CodeType for CustomCodeType { + fn type_label(&self) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs new file mode 100644 index 0000000000..14377ed9de --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/enum_.rs @@ -0,0 +1,35 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; + +#[derive(Debug)] +pub struct EnumCodeType { + id: String, +} + +impl EnumCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for EnumCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } + + fn literal(&self, literal: &Literal) -> String { + if let Literal::Enum(v, _) = literal { + format!(".{}", super::SwiftCodeOracle.enum_variant_name(v)) + } else { + unreachable!(); + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs new file mode 100644 index 0000000000..b488b004cf --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs @@ -0,0 +1,23 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ForeignExecutorCodeType; + +impl CodeType for ForeignExecutorCodeType { + fn type_label(&self) -> String { + // On Swift, we define a struct to represent a ForeignExecutor + "UniFfiForeignExecutor".into() + } + + fn canonical_name(&self) -> String { + "ForeignExecutor".into() + } + + fn initialization_fn(&self) -> Option<String> { + Some("uniffiInitForeignExecutor".into()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs new file mode 100644 index 0000000000..0b6728ba84 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ExternalCodeType { + name: String, +} + +impl ExternalCodeType { + pub fn new(name: String) -> Self { + ExternalCodeType { name } + } +} + +impl CodeType for ExternalCodeType { + fn type_label(&self) -> String { + self.name.clone() + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.name) + } + + // lower and lift need to call public function which were generated for + // the original types. + fn lower(&self) -> String { + format!("{}_lower", self.ffi_converter_name()) + } + + fn lift(&self) -> String { + format!("{}_lift", self.ffi_converter_name()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs new file mode 100644 index 0000000000..c45091c80a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/miscellany.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct TimestampCodeType; + +impl CodeType for TimestampCodeType { + fn type_label(&self) -> String { + "Date".into() + } + + fn canonical_name(&self) -> String { + "Timestamp".into() + } +} + +#[derive(Debug)] +pub struct DurationCodeType; + +impl CodeType for DurationCodeType { + fn type_label(&self) -> String { + "TimeInterval".into() + } + + fn canonical_name(&self) -> String { + "Duration".into() + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs new file mode 100644 index 0000000000..12db4afc66 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -0,0 +1,688 @@ +/* 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/. */ + +use once_cell::sync::Lazy; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Debug; + +use anyhow::{Context, Result}; +use askama::Template; +use heck::{ToLowerCamelCase, ToUpperCamelCase}; +use serde::{Deserialize, Serialize}; + +use super::Bindings; +use crate::backend::TemplateExpression; +use crate::interface::*; +use crate::BindingsConfig; + +mod callback_interface; +mod compounds; +mod custom; +mod enum_; +mod executor; +mod external; +mod miscellany; +mod object; +mod primitives; +mod record; + +/// A trait tor the implementation. +trait CodeType: Debug { + /// The language specific label used to reference this type. This will be used in + /// method signatures and property declarations. + fn type_label(&self) -> String; + + /// A representation of this type label that can be used as part of another + /// identifier. e.g. `read_foo()`, or `FooInternals`. + /// + /// This is especially useful when creating specialized objects or methods to deal + /// with this type only. + fn canonical_name(&self) -> String { + self.type_label() + } + + fn literal(&self, _literal: &Literal) -> String { + unimplemented!("Unimplemented for {}", self.type_label()) + } + + /// Name of the FfiConverter + /// + /// This is the object that contains the lower, write, lift, and read methods for this type. + fn ffi_converter_name(&self) -> String { + format!("FfiConverter{}", self.canonical_name()) + } + + // XXX - the below should be removed and replace with the ffi_converter_name reference in the template. + /// An expression for lowering a value into something we can pass over the FFI. + fn lower(&self) -> String { + format!("{}.lower", self.ffi_converter_name()) + } + + /// An expression for writing a value into a byte buffer. + fn write(&self) -> String { + format!("{}.write", self.ffi_converter_name()) + } + + /// An expression for lifting a value from something we received over the FFI. + fn lift(&self) -> String { + format!("{}.lift", self.ffi_converter_name()) + } + + /// An expression for reading a value from a byte buffer. + fn read(&self) -> String { + format!("{}.read", self.ffi_converter_name()) + } + + /// A list of imports that are needed if this type is in use. + /// Classes are imported exactly once. + fn imports(&self) -> Option<Vec<String>> { + None + } + + /// Function to run at startup + fn initialization_fn(&self) -> Option<String> { + None + } +} + +/// From <https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Keywords-and-Punctuation> +static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| { + [ + // Keywords used in declarations: + "associatedtype", + "class", + "deinit", + "enum", + "extension", + "fileprivate", + "func", + "import", + "init", + "inout", + "internal", + "let", + "open", + "operator", + "private", + "precedencegroup", + "protocol", + "public", + "rethrows", + "static", + "struct", + "subscript", + "typealias", + "var", + // Keywords used in statements: + "break", + "case", + "catch", + "continue", + "default", + "defer", + "do", + "else", + "fallthrough", + "for", + "guard", + "if", + "in", + "repeat", + "return", + "throw", + "switch", + "where", + "while", + // Keywords used in expressions and types: + "Any", + "as", + "await", + "catch", + "false", + "is", + "nil", + "rethrows", + "self", + "Self", + "super", + "throw", + "throws", + "true", + "try", + ] + .iter() + .map(ToString::to_string) + .collect::<HashSet<_>>() +}); + +/// Quote a name for use in a context where keywords must be quoted +pub fn quote_general_keyword(nm: String) -> String { + if KEYWORDS.contains(&nm) { + format!("`{nm}`") + } else { + nm + } +} + +/// Per <https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Keywords-and-Punctuation> subset of keywords which need quoting in arg context. +static ARG_KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| { + ["inout", "var", "let"] + .iter() + .map(ToString::to_string) + .collect::<HashSet<_>>() +}); + +/// Quote a name for use in arg context where fewer keywords must be quoted +pub fn quote_arg_keyword(nm: String) -> String { + if ARG_KEYWORDS.contains(&nm) { + format!("`{nm}`") + } else { + nm + } +} + +/// Config options for the caller to customize the generated Swift. +/// +/// Note that this can only be used to control details of the Swift *that do not affect the underlying component*, +/// since the details of the underlying component are entirely determined by the `ComponentInterface`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + cdylib_name: Option<String>, + module_name: Option<String>, + ffi_module_name: Option<String>, + ffi_module_filename: Option<String>, + generate_module_map: Option<bool>, + omit_argument_labels: Option<bool>, + #[serde(default)] + custom_types: HashMap<String, CustomTypeConfig>, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct CustomTypeConfig { + imports: Option<Vec<String>>, + type_name: Option<String>, + into_custom: TemplateExpression, + from_custom: TemplateExpression, +} + +impl Config { + /// The name of the Swift module containing the high-level foreign-language bindings. + pub fn module_name(&self) -> String { + match self.module_name.as_ref() { + Some(name) => name.clone(), + None => "uniffi".into(), + } + } + + /// The name of the lower-level C module containing the FFI declarations. + pub fn ffi_module_name(&self) -> String { + match self.ffi_module_name.as_ref() { + Some(name) => name.clone(), + None => format!("{}FFI", self.module_name()), + } + } + + /// The filename stem for the lower-level C module containing the FFI declarations. + pub fn ffi_module_filename(&self) -> String { + match self.ffi_module_filename.as_ref() { + Some(name) => name.clone(), + None => self.ffi_module_name(), + } + } + + /// The name of the `.modulemap` file for the lower-level C module with FFI declarations. + pub fn modulemap_filename(&self) -> String { + format!("{}.modulemap", self.ffi_module_filename()) + } + + /// The name of the `.h` file for the lower-level C module with FFI declarations. + pub fn header_filename(&self) -> String { + format!("{}.h", self.ffi_module_filename()) + } + + /// The name of the compiled Rust library containing the FFI implementation. + pub fn cdylib_name(&self) -> String { + if let Some(cdylib_name) = &self.cdylib_name { + cdylib_name.clone() + } else { + "uniffi".into() + } + } + + /// Whether to generate a `.modulemap` file for the lower-level C module with FFI declarations. + pub fn generate_module_map(&self) -> bool { + self.generate_module_map.unwrap_or(true) + } + + /// Whether to omit argument labels in Swift function definitions. + pub fn omit_argument_labels(&self) -> bool { + self.omit_argument_labels.unwrap_or(false) + } +} + +impl BindingsConfig for Config { + fn update_from_ci(&mut self, ci: &ComponentInterface) { + self.module_name + .get_or_insert_with(|| ci.namespace().into()); + self.cdylib_name + .get_or_insert_with(|| format!("uniffi_{}", ci.namespace())); + } + + fn update_from_cdylib_name(&mut self, cdylib_name: &str) { + self.cdylib_name + .get_or_insert_with(|| cdylib_name.to_string()); + } + + fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} +} + +/// Generate UniFFI component bindings for Swift, as strings in memory. +/// +pub fn generate_bindings(config: &Config, ci: &ComponentInterface) -> Result<Bindings> { + let header = BridgingHeader::new(config, ci) + .render() + .context("failed to render Swift bridging header")?; + let library = SwiftWrapper::new(config.clone(), ci) + .render() + .context("failed to render Swift library")?; + let modulemap = if config.generate_module_map() { + Some( + ModuleMap::new(config, ci) + .render() + .context("failed to render Swift modulemap")?, + ) + } else { + None + }; + Ok(Bindings { + library, + header, + modulemap, + }) +} + +/// Renders Swift helper code for all types +/// +/// This template is a bit different than others in that it stores internal state from the render +/// process. Make sure to only call `render()` once. +#[derive(Template)] +#[template(syntax = "swift", escape = "none", path = "Types.swift")] +pub struct TypeRenderer<'a> { + config: &'a Config, + ci: &'a ComponentInterface, + // Track included modules for the `include_once()` macro + include_once_names: RefCell<HashSet<String>>, + // Track imports added with the `add_import()` macro + imports: RefCell<BTreeSet<String>>, +} + +impl<'a> TypeRenderer<'a> { + fn new(config: &'a Config, ci: &'a ComponentInterface) -> Self { + Self { + config, + ci, + include_once_names: RefCell::new(HashSet::new()), + imports: RefCell::new(BTreeSet::new()), + } + } + + // The following methods are used by the `Types.swift` macros. + + // Helper for the including a template, but only once. + // + // The first time this is called with a name it will return true, indicating that we should + // include the template. Subsequent calls will return false. + fn include_once_check(&self, name: &str) -> bool { + self.include_once_names + .borrow_mut() + .insert(name.to_string()) + } + + // Helper to add an import statement + // + // Call this inside your template to cause an import statement to be added at the top of the + // file. Imports will be sorted and de-deuped. + // + // Returns an empty string so that it can be used inside an askama `{{ }}` block. + fn add_import(&self, name: &str) -> &str { + self.imports.borrow_mut().insert(name.to_owned()); + "" + } +} + +/// Template for generating the `.h` file that defines the low-level C FFI. +/// +/// This file defines only the low-level structs and functions that are exposed +/// by the compiled Rust code. It gets wrapped into a higher-level API by the +/// code from [`SwiftWrapper`]. +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "BridgingHeaderTemplate.h")] +pub struct BridgingHeader<'config, 'ci> { + _config: &'config Config, + ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> BridgingHeader<'config, 'ci> { + pub fn new(config: &'config Config, ci: &'ci ComponentInterface) -> Self { + Self { + _config: config, + ci, + } + } +} + +/// Template for generating the `.modulemap` file that exposes the low-level C FFI. +/// +/// This file defines how the low-level C FFI from [`BridgingHeader`] gets exposed +/// as a Swift module that can be called by other Swift code. In our case, its only +/// job is to define the *name* of the Swift module that will contain the FFI functions +/// so that it can be imported by the higher-level code in from [`SwiftWrapper`]. +#[derive(Template)] +#[template(syntax = "c", escape = "none", path = "ModuleMapTemplate.modulemap")] +pub struct ModuleMap<'config, 'ci> { + config: &'config Config, + _ci: &'ci ComponentInterface, +} + +impl<'config, 'ci> ModuleMap<'config, 'ci> { + pub fn new(config: &'config Config, _ci: &'ci ComponentInterface) -> Self { + Self { config, _ci } + } +} + +#[derive(Template)] +#[template(syntax = "swift", escape = "none", path = "wrapper.swift")] +pub struct SwiftWrapper<'a> { + config: Config, + ci: &'a ComponentInterface, + type_helper_code: String, + type_imports: BTreeSet<String>, + has_async_fns: bool, +} +impl<'a> SwiftWrapper<'a> { + pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { + let type_renderer = TypeRenderer::new(&config, ci); + let type_helper_code = type_renderer.render().unwrap(); + let type_imports = type_renderer.imports.into_inner(); + Self { + config, + ci, + type_helper_code, + type_imports, + has_async_fns: ci.has_async_fns(), + } + } + + pub fn imports(&self) -> Vec<String> { + self.type_imports.iter().cloned().collect() + } + + pub fn initialization_fns(&self) -> Vec<String> { + self.ci + .iter_types() + .map(|t| SwiftCodeOracle.find(t)) + .filter_map(|ct| ct.initialization_fn()) + .chain( + self.has_async_fns + .then(|| "uniffiInitContinuationCallback".into()), + ) + .collect() + } +} + +#[derive(Clone)] +pub struct SwiftCodeOracle; + +impl SwiftCodeOracle { + // Map `Type` instances to a `Box<dyn CodeType>` for that type. + // + // There is a companion match in `templates/Types.swift` which performs a similar function for the + // template code. + // + // - When adding additional types here, make sure to also add a match arm to the `Types.swift` template. + // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + fn create_code_type(&self, type_: Type) -> Box<dyn CodeType> { + match type_ { + Type::UInt8 => Box::new(primitives::UInt8CodeType), + Type::Int8 => Box::new(primitives::Int8CodeType), + Type::UInt16 => Box::new(primitives::UInt16CodeType), + Type::Int16 => Box::new(primitives::Int16CodeType), + Type::UInt32 => Box::new(primitives::UInt32CodeType), + Type::Int32 => Box::new(primitives::Int32CodeType), + Type::UInt64 => Box::new(primitives::UInt64CodeType), + Type::Int64 => Box::new(primitives::Int64CodeType), + Type::Float32 => Box::new(primitives::Float32CodeType), + Type::Float64 => Box::new(primitives::Float64CodeType), + Type::Boolean => Box::new(primitives::BooleanCodeType), + Type::String => Box::new(primitives::StringCodeType), + Type::Bytes => Box::new(primitives::BytesCodeType), + + Type::Timestamp => Box::new(miscellany::TimestampCodeType), + Type::Duration => Box::new(miscellany::DurationCodeType), + + Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), + Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), + Type::CallbackInterface { name, .. } => { + Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) + } + Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), + Type::Optional { inner_type } => { + Box::new(compounds::OptionalCodeType::new(*inner_type)) + } + Type::Sequence { inner_type } => { + Box::new(compounds::SequenceCodeType::new(*inner_type)) + } + Type::Map { + key_type, + value_type, + } => Box::new(compounds::MapCodeType::new(*key_type, *value_type)), + Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)), + Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)), + } + } + + fn find(&self, type_: &Type) -> Box<dyn CodeType> { + self.create_code_type(type_.clone()) + } + + /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc). + fn class_name(&self, nm: &str) -> String { + nm.to_string().to_upper_camel_case() + } + + /// Get the idiomatic Swift rendering of a function name. + fn fn_name(&self, nm: &str) -> String { + nm.to_string().to_lower_camel_case() + } + + /// Get the idiomatic Swift rendering of a variable name. + fn var_name(&self, nm: &str) -> String { + nm.to_string().to_lower_camel_case() + } + + /// Get the idiomatic Swift rendering of an individual enum variant. + fn enum_variant_name(&self, nm: &str) -> String { + nm.to_string().to_lower_camel_case() + } + + fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::Int8 => "Int8".into(), + FfiType::UInt8 => "UInt8".into(), + FfiType::Int16 => "Int16".into(), + FfiType::UInt16 => "UInt16".into(), + FfiType::Int32 => "Int32".into(), + FfiType::UInt32 => "UInt32".into(), + FfiType::Int64 => "Int64".into(), + FfiType::UInt64 => "UInt64".into(), + FfiType::Float32 => "Float".into(), + FfiType::Float64 => "Double".into(), + FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), + FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::ForeignCallback => "ForeignCallback".into(), + FfiType::ForeignExecutorHandle => "Int".into(), + FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), + FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), + FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { + "UnsafeMutableRawPointer".into() + } + } + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::ForeignCallback + | FfiType::ForeignExecutorCallback + | FfiType::RustFutureHandle + | FfiType::RustFutureContinuationCallback + | FfiType::RustFutureContinuationData => { + format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) + } + _ => self.ffi_type_label_raw(ffi_type), + } + } + + fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String { + self.ffi_type_label_raw(ffi_type) + } +} + +pub mod filters { + use super::*; + pub use crate::backend::filters::*; + + fn oracle() -> &'static SwiftCodeOracle { + &SwiftCodeOracle + } + + pub fn type_name(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).type_label()) + } + + pub fn canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).canonical_name()) + } + + pub fn ffi_converter_name(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).ffi_converter_name()) + } + + pub fn lower_fn(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).lower()) + } + + pub fn write_fn(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).write()) + } + + pub fn lift_fn(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).lift()) + } + + pub fn read_fn(as_type: &impl AsType) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).read()) + } + + pub fn literal_swift( + literal: &Literal, + as_type: &impl AsType, + ) -> Result<String, askama::Error> { + Ok(oracle().find(&as_type.as_type()).literal(literal)) + } + + /// Get the Swift type for an FFIType + pub fn ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> { + Ok(oracle().ffi_type_label(ffi_type)) + } + + pub fn ffi_canonical_name(ffi_type: &FfiType) -> Result<String, askama::Error> { + Ok(oracle().ffi_canonical_name(ffi_type)) + } + + /// Like `ffi_type_name`, but used in `BridgingHeaderTemplate.h` which uses a slightly different + /// names. + pub fn header_ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> { + Ok(match ffi_type { + FfiType::Int8 => "int8_t".into(), + FfiType::UInt8 => "uint8_t".into(), + FfiType::Int16 => "int16_t".into(), + FfiType::UInt16 => "uint16_t".into(), + FfiType::Int32 => "int32_t".into(), + FfiType::UInt32 => "uint32_t".into(), + FfiType::Int64 => "int64_t".into(), + FfiType::UInt64 => "uint64_t".into(), + FfiType::Float32 => "float".into(), + FfiType::Float64 => "double".into(), + FfiType::RustArcPtr(_) => "void*_Nonnull".into(), + FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::ForeignBytes => "ForeignBytes".into(), + FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), + FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), + FfiType::ForeignExecutorHandle => "size_t".into(), + FfiType::RustFutureContinuationCallback => { + "UniFfiRustFutureContinuation _Nonnull".into() + } + FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { + "void* _Nonnull".into() + } + }) + } + + /// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc). + pub fn class_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().class_name(nm)) + } + + /// Get the idiomatic Swift rendering of a function name. + pub fn fn_name(nm: &str) -> Result<String, askama::Error> { + Ok(quote_general_keyword(oracle().fn_name(nm))) + } + + /// Get the idiomatic Swift rendering of a variable name. + pub fn var_name(nm: &str) -> Result<String, askama::Error> { + Ok(quote_general_keyword(oracle().var_name(nm))) + } + + /// Get the idiomatic Swift rendering of an arguments name. + /// This is the same as the var name but quoting is not required. + pub fn arg_name(nm: &str) -> Result<String, askama::Error> { + Ok(quote_arg_keyword(oracle().var_name(nm))) + } + + /// Get the idiomatic Swift rendering of an individual enum variant, quoted if it is a keyword (for use in e.g. declarations) + pub fn enum_variant_swift_quoted(nm: &str) -> Result<String, askama::Error> { + Ok(quote_general_keyword(oracle().enum_variant_name(nm))) + } + + /// Get the idiomatic Swift rendering of an individual enum variant, for contexts (for use in non-declaration contexts where quoting is not needed) + pub fn enum_variant_swift(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().enum_variant_name(nm)) + } + + pub fn error_handler(result: &ResultType) -> Result<String, askama::Error> { + Ok(match &result.throws_type { + Some(t) => format!("{}.lift", ffi_converter_name(t)?), + None => "nil".into(), + }) + } + + /// Name of the callback function to handle an async result + pub fn future_callback(result: &ResultType) -> Result<String, askama::Error> { + Ok(format!( + "uniffiFutureCallbackHandler{}{}", + match &result.return_type { + Some(t) => SwiftCodeOracle.find(t).canonical_name(), + None => "Void".into(), + }, + match &result.throws_type { + Some(t) => SwiftCodeOracle.find(t).canonical_name(), + None => "".into(), + } + )) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs new file mode 100644 index 0000000000..ea140c998d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct ObjectCodeType { + id: String, +} + +impl ObjectCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for ObjectCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs new file mode 100644 index 0000000000..86424658a3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -0,0 +1,91 @@ +/* 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/. */ + +use super::CodeType; +use crate::backend::Literal; +use crate::interface::{Radix, Type}; +use paste::paste; + +fn render_literal(literal: &Literal) -> String { + fn typed_number(type_: &Type, num_str: String) -> String { + match type_ { + // special case Int32. + Type::Int32 => num_str, + // otherwise use constructor e.g. UInt8(x) + Type::Int8 + | Type::UInt8 + | Type::Int16 + | Type::UInt16 + | Type::UInt32 + | Type::Int64 + | Type::UInt64 + | Type::Float32 + | Type::Float64 => + // XXX we should pass in the codetype itself. + { + format!( + "{}({num_str})", + super::SwiftCodeOracle.find(type_).type_label() + ) + } + _ => panic!("Unexpected literal: {num_str} is not a number"), + } + } + + match literal { + Literal::Boolean(v) => format!("{v}"), + Literal::String(s) => format!("\"{s}\""), + Literal::Int(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::UInt(i, radix, type_) => typed_number( + type_, + match radix { + Radix::Octal => format!("0o{i:o}"), + Radix::Decimal => format!("{i}"), + Radix::Hexadecimal => format!("{i:#x}"), + }, + ), + Literal::Float(string, type_) => typed_number(type_, string.clone()), + _ => unreachable!("Literal"), + } +} + +macro_rules! impl_code_type_for_primitive { + ($T:ty, $class_name:literal) => { + paste! { + #[derive(Debug)] + pub struct $T; + + impl CodeType for $T { + fn type_label(&self) -> String { + $class_name.into() + } + + fn literal(&self, literal: &Literal) -> String { + render_literal(&literal) + } + } + } + }; +} + +impl_code_type_for_primitive!(BooleanCodeType, "Bool"); +impl_code_type_for_primitive!(StringCodeType, "String"); +impl_code_type_for_primitive!(BytesCodeType, "Data"); +impl_code_type_for_primitive!(Int8CodeType, "Int8"); +impl_code_type_for_primitive!(Int16CodeType, "Int16"); +impl_code_type_for_primitive!(Int32CodeType, "Int32"); +impl_code_type_for_primitive!(Int64CodeType, "Int64"); +impl_code_type_for_primitive!(UInt8CodeType, "UInt8"); +impl_code_type_for_primitive!(UInt16CodeType, "UInt16"); +impl_code_type_for_primitive!(UInt32CodeType, "UInt32"); +impl_code_type_for_primitive!(UInt64CodeType, "UInt64"); +impl_code_type_for_primitive!(Float32CodeType, "Float"); +impl_code_type_for_primitive!(Float64CodeType, "Double"); diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs new file mode 100644 index 0000000000..401109011f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/record.rs @@ -0,0 +1,26 @@ +/* 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/. */ + +use super::CodeType; + +#[derive(Debug)] +pub struct RecordCodeType { + id: String, +} + +impl RecordCodeType { + pub fn new(id: String) -> Self { + Self { id } + } +} + +impl CodeType for RecordCodeType { + fn type_label(&self) -> String { + super::SwiftCodeOracle.class_name(&self.id) + } + + fn canonical_name(&self) -> String { + format!("Type{}", self.id) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs new file mode 100644 index 0000000000..bf17f38a4e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/mod.rs @@ -0,0 +1,97 @@ +/* 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/. */ + +//! # Swift bindings backend for UniFFI +//! +//! This module generates Swift bindings from a [`ComponentInterface`] definition, +//! using Swift's builtin support for loading C header files. +//! +//! Conceptually, the generated bindings are split into two Swift modules, one for the low-level +//! C FFI layer and one for the higher-level Swift bindings. For a UniFFI component named "example" +//! we generate: +//! +//! * A C header file `exampleFFI.h` declaring the low-level structs and functions for calling +//! into Rust, along with a corresponding `exampleFFI.modulemap` to expose them to Swift. +//! +//! * A Swift source file `example.swift` that imports the `exampleFFI` module and wraps it +//! to provide the higher-level Swift API. +//! +//! Most of the concepts in a [`ComponentInterface`] have an obvious counterpart in Swift, +//! with the details documented in inline comments where appropriate. +//! +//! To handle lifting/lowering/serializing types across the FFI boundary, the Swift code +//! defines a `protocol ViaFfi` that is analogous to the `uniffi::ViaFfi` Rust trait. +//! Each type that can traverse the FFI conforms to the `ViaFfi` protocol, which specifies: +//! +//! * The corresponding low-level type. +//! * How to lift from and lower into into that type. +//! * How to read from and write into a byte buffer. +//! + +use std::process::Command; + +use anyhow::Result; +use camino::Utf8Path; +use fs_err as fs; + +pub mod gen_swift; +pub use gen_swift::{generate_bindings, Config}; +mod test; + +use super::super::interface::ComponentInterface; +pub use test::{run_script, run_test}; + +/// The Swift bindings generated from a [`ComponentInterface`]. +/// +pub struct Bindings { + /// The contents of the generated `.swift` file, as a string. + library: String, + /// The contents of the generated `.h` file, as a string. + header: String, + /// The contents of the generated `.modulemap` file, as a string. + modulemap: Option<String>, +} + +/// Write UniFFI component bindings for Swift as files on disk. +/// +/// Unlike other target languages, binding to Rust code from Swift involves more than just +/// generating a `.swift` file. We also need to produce a `.h` file with the C-level API +/// declarations, and a `.modulemap` file to tell Swift how to use it. +pub fn write_bindings( + config: &Config, + ci: &ComponentInterface, + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<()> { + let Bindings { + header, + library, + modulemap, + } = generate_bindings(config, ci)?; + + let source_file = out_dir.join(format!("{}.swift", config.module_name())); + fs::write(&source_file, library)?; + + let header_file = out_dir.join(config.header_filename()); + fs::write(header_file, header)?; + + if let Some(modulemap) = modulemap { + let modulemap_file = out_dir.join(config.modulemap_filename()); + fs::write(modulemap_file, modulemap)?; + } + + if try_format_code { + if let Err(e) = Command::new("swiftformat") + .arg(source_file.as_str()) + .output() + { + println!( + "Warning: Unable to auto-format {} using swiftformat: {e:?}", + source_file.file_name().unwrap(), + ); + } + } + + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift new file mode 100644 index 0000000000..695208861d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -0,0 +1,62 @@ +private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 +private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 + +fileprivate func uniffiRustCallAsync<F, T>( + rustFutureFunc: () -> UnsafeMutableRawPointer, + pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (), + completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F, + freeFunc: (UnsafeMutableRawPointer) -> (), + liftFunc: (F) throws -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) async throws -> T { + // Make sure to call uniffiEnsureInitialized() since future creation doesn't have a + // RustCallStatus param, so doesn't use makeRustCall() + uniffiEnsureInitialized() + let rustFuture = rustFutureFunc() + defer { + freeFunc(rustFuture) + } + var pollResult: Int8; + repeat { + pollResult = await withUnsafeContinuation { + pollFunc(rustFuture, ContinuationHolder($0).toOpaque()) + } + } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY + + return try liftFunc(makeRustCall( + { completeFunc(rustFuture, $0) }, + errorHandler: errorHandler + )) +} + +// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { + ContinuationHolder.fromOpaque(ptr).resume(pollResult) +} + +// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across +// the FFI +fileprivate class ContinuationHolder { + let continuation: UnsafeContinuation<Int8, Never> + + init(_ continuation: UnsafeContinuation<Int8, Never>) { + self.continuation = continuation + } + + func resume(_ pollResult: Int8) { + self.continuation.resume(returning: pollResult) + } + + func toOpaque() -> UnsafeMutableRawPointer { + return Unmanaged<ContinuationHolder>.passRetained(self).toOpaque() + } + + static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { + return Unmanaged<ContinuationHolder>.fromOpaque(ptr).takeRetainedValue() + } +} + +fileprivate func uniffiInitContinuationCallback() { + {{ ci.ffi_rust_future_continuation_callback_set().name() }}(uniffiFutureContinuationCallback) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift new file mode 100644 index 0000000000..465e519628 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BooleanHelper.swift @@ -0,0 +1,20 @@ +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h new file mode 100644 index 0000000000..87698e359f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -0,0 +1,79 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); + +// Task defined in Rust that Swift executes +typedef void (*UniFfiRustTaskCallback)(const void * _Nullable, int8_t); + +// Callback to execute Rust tasks using a Swift Task +// +// Args: +// executor: ForeignExecutor lowered into a size_t value +// delay: Delay in MS +// task: UniFfiRustTaskCallback to call +// task_data: data to pass the task callback +typedef int8_t (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +// Continuation callback for UniFFI Futures +typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); + +// Scaffolding functions +{%- for func in ci.iter_ffi_function_definitions() %} +{% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( + {%- if func.arguments().len() > 0 %} + {%- for arg in func.arguments() %} + {{- arg.type_().borrow()|header_ffi_type_name }} {{ arg.name() -}}{% if !loop.last || func.has_rust_call_status_arg() %}, {% endif %} + {%- endfor %} + {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{% endif %} + {%- else %} + {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %} + {% endif %} +); +{%- endfor %} + +{% import "macros.swift" as swift %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift new file mode 100644 index 0000000000..9ae62d1667 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -0,0 +1,64 @@ +fileprivate extension NSLock { + func withLock<T>(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate typealias UniFFICallbackHandle = UInt64 +fileprivate class UniFFICallbackHandleMap<T> { + private var leftMap: [UniFFICallbackHandle: T] = [:] + private var counter: [UniFFICallbackHandle: UInt64] = [:] + private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:] + + private let lock = NSLock() + private var currentHandle: UniFFICallbackHandle = 0 + private let stride: UniFFICallbackHandle = 1 + + func insert(obj: T) -> UniFFICallbackHandle { + lock.withLock { + let id = ObjectIdentifier(obj as AnyObject) + let handle = rightMap[id] ?? { + currentHandle += stride + let handle = currentHandle + leftMap[handle] = obj + rightMap[id] = handle + return handle + }() + counter[handle] = (counter[handle] ?? 0) + 1 + return handle + } + } + + func get(handle: UniFFICallbackHandle) -> T? { + lock.withLock { + leftMap[handle] + } + } + + func delete(handle: UniFFICallbackHandle) { + remove(handle: handle) + } + + @discardableResult + func remove(handle: UniFFICallbackHandle) -> T? { + lock.withLock { + defer { counter[handle] = (counter[handle] ?? 1) - 1 } + guard counter[handle] == 1 else { return leftMap[handle] } + let obj = leftMap.removeValue(forKey: handle) + if let obj = obj { + rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) + } + return obj + } + } +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +private let IDX_CALLBACK_FREE: Int32 = 0 +// Callback return codes +private let UNIFFI_CALLBACK_SUCCESS: Int32 = 0 +private let UNIFFI_CALLBACK_ERROR: Int32 = 1 +private let UNIFFI_CALLBACK_UNEXPECTED_ERROR: Int32 = 2 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift new file mode 100644 index 0000000000..aec8ded930 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -0,0 +1,150 @@ +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} +{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} + +// Declaration and FfiConverters for {{ type_name }} Callback Interface + +public protocol {{ type_name }} : AnyObject { + {% for meth in cbi.methods() -%} + func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %} -> {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + +// The ForeignCallback that is passed to Rust. +fileprivate let {{ foreign_callback }} : ForeignCallback = + { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer<UInt8>, argsLen: Int32, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in + {% for meth in cbi.methods() -%} + {%- let method_name = format!("invoke_{}", meth.name())|fn_name %} + + func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer<UInt8>, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer<RustBuffer>) throws -> Int32 { + {%- if meth.arguments().len() > 0 %} + var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen))) + {%- endif %} + + {%- match meth.return_type() %} + {%- when Some(return_type) %} + func makeCall() throws -> Int32 { + let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}( + {% for arg in meth.arguments() -%} + {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ) + var writer = [UInt8]() + {{ return_type|write_fn }}(result, into: &writer) + out_buf.pointee = RustBuffer(bytes: writer) + return UNIFFI_CALLBACK_SUCCESS + } + {%- when None %} + func makeCall() throws -> Int32 { + try swiftCallbackInterface.{{ meth.name()|fn_name }}( + {% for arg in meth.arguments() -%} + {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ) + return UNIFFI_CALLBACK_SUCCESS + } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + return try makeCall() + {%- when Some(error_type) %} + do { + return try makeCall() + } catch let error as {{ error_type|type_name }} { + out_buf.pointee = {{ error_type|lower_fn }}(error) + return UNIFFI_CALLBACK_ERROR + } + {%- endmatch %} + } + {%- endfor %} + + + switch method { + case IDX_CALLBACK_FREE: + {{ ffi_converter_name }}.drop(handle: handle) + // Sucessful return + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + return UNIFFI_CALLBACK_SUCCESS + {% for meth in cbi.methods() -%} + {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} + case {{ loop.index }}: + let cb: {{ cbi|type_name }} + do { + cb = try {{ ffi_converter_name }}.lift(handle) + } catch { + out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle") + return UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + do { + return try {{ method_name }}(cb, argsData, argsLen, out_buf) + } catch let error { + out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + return UNIFFI_CALLBACK_UNEXPECTED_ERROR + } + {% endfor %} + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: + // An unexpected error happened. + // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` + return UNIFFI_CALLBACK_UNEXPECTED_ERROR + } +} + +// FfiConverter protocol for callback interfaces +fileprivate struct {{ ffi_converter_name }} { + private static let initCallbackOnce: () = { + // Swift ensures this initializer code will once run once, even when accessed by multiple threads. + try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in + {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err) + } + }() + + private static func ensureCallbackinitialized() { + _ = initCallbackOnce + } + + static func drop(handle: UniFFICallbackHandle) { + handleMap.remove(handle: handle) + } + + private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() +} + +extension {{ ffi_converter_name }} : FfiConverter { + typealias SwiftType = {{ type_name }} + // We can use Handle as the FfiType because it's a typealias to UInt64 + typealias FfiType = UniFFICallbackHandle + + public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { + ensureCallbackinitialized(); + guard let callback = handleMap.get(handle: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return callback + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + ensureCallbackinitialized(); + let handle: UniFFICallbackHandle = try readInt(&buf) + return try lift(handle) + } + + public static func lower(_ v: SwiftType) -> UniFFICallbackHandle { + ensureCallbackinitialized(); + return handleMap.insert(obj: v) + } + + public static func write(_ v: SwiftType, into buf: inout [UInt8]) { + ensureCallbackinitialized(); + writeInt(&buf, lower(v)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift new file mode 100644 index 0000000000..504cadfc1d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CustomType.swift @@ -0,0 +1,86 @@ +{%- let ffi_type_name=builtin|ffi_type|ffi_type_name %} +{%- match config.custom_types.get(name.as_str()) %} +{%- when None %} +{#- No config, just forward all methods to our builtin type #} +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias {{ name }} = {{ builtin|type_name }} +public struct FfiConverterType{{ name }}: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + return try {{ builtin|read_fn }}(from: &buf) + } + + public static func write(_ value: {{ name }}, into buf: inout [UInt8]) { + return {{ builtin|write_fn }}(value, into: &buf) + } + + public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + return try {{ builtin|lift_fn }}(value) + } + + public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + return {{ builtin|lower_fn }}(value) + } +} + +{%- when Some with (config) %} + +{# When the config specifies a different type name, create a typealias for it #} +{%- match config.type_name %} +{%- when Some with (concrete_type_name) %} +/** + * Typealias from the type name used in the UDL file to the custom type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias {{ name }} = {{ concrete_type_name }} +{%- else %} +{%- endmatch %} + +{%- match config.imports %} +{%- when Some(imports) %} +{%- for import_name in imports %} +{{ self.add_import(import_name) }} +{%- endfor %} +{%- else %} +{%- endmatch %} + +public struct FfiConverterType{{ name }}: FfiConverter { + {#- Custom type config supplied, use it to convert the builtin type #} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ name }} { + let builtinValue = try {{ builtin|read_fn }}(from: &buf) + return {{ config.into_custom.render("builtinValue") }} + } + + public static func write(_ value: {{ name }}, into buf: inout [UInt8]) { + let builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|write_fn }}(builtinValue, into: &buf) + } + + public static func lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + let builtinValue = try {{ builtin|lift_fn }}(value) + return {{ config.into_custom.render("builtinValue") }} + } + + public static func lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + let builtinValue = {{ config.from_custom.render("value") }} + return {{ builtin|lower_fn }}(builtinValue) + } +} + +{%- endmatch %} + +{# +We always write these public functions just incase the type is used as +an external type by another crate. +#} +public func FfiConverterType{{ name }}_lift(_ value: {{ ffi_type_name }}) throws -> {{ name }} { + return try FfiConverterType{{ name }}.lift(value) +} + +public func FfiConverterType{{ name }}_lower(_ value: {{ name }}) -> {{ ffi_type_name }} { + return FfiConverterType{{ name }}.lower(value) +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift new file mode 100644 index 0000000000..7db240bf9c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DataHelper.swift @@ -0,0 +1,14 @@ +fileprivate struct FfiConverterData: FfiConverterRustBuffer { + typealias SwiftType = Data + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Data { + let len: Int32 = try readInt(&buf) + return Data(try readBytes(&buf, count: Int(len))) + } + + public static func write(_ value: Data, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + writeBytes(&buf, value) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift new file mode 100644 index 0000000000..c2aa49e9d1 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/DurationHelper.swift @@ -0,0 +1,24 @@ +fileprivate struct FfiConverterDuration: FfiConverterRustBuffer { + typealias SwiftType = TimeInterval + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TimeInterval { + let seconds: UInt64 = try readInt(&buf) + let nanoseconds: UInt32 = try readInt(&buf) + return Double(seconds) + (Double(nanoseconds) / 1.0e9) + } + + public static func write(_ value: TimeInterval, into buf: inout [UInt8]) { + if value.rounded(.down) > Double(Int64.max) { + fatalError("Duration overflow, exceeds max bounds supported by Uniffi") + } + + if value < 0 { + fatalError("Invalid duration, must be non-negative") + } + + let seconds = UInt64(value) + let nanoseconds = UInt32((value - Double(seconds)) * 1.0e9) + writeInt(&buf, seconds) + writeInt(&buf, nanoseconds) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift new file mode 100644 index 0000000000..99f45290cc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -0,0 +1,59 @@ +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum {{ type_name }} { + {% for variant in e.variants() %} + case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {% endfor %} +} + +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let variant: Int32 = try readInt(&buf) + switch variant { + {% for variant in e.variants() %} + case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift_quoted }}{% if variant.has_fields() %}( + {%- for field in variant.fields() %} + {{ field.name()|arg_name }}: try {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} + {%- endfor %} + ){%- endif %} + {% endfor %} + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + switch value { + {% for variant in e.variants() %} + {% if variant.has_fields() %} + case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): + writeInt(&buf, Int32({{ loop.index }})) + {% for field in variant.fields() -%} + {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) + {% endfor -%} + {% else %} + case .{{ variant.name()|enum_variant_swift_quoted }}: + writeInt(&buf, Int32({{ loop.index }})) + {% endif %} + {%- endfor %} + } + } +} + +{# +We always write these public functions just in case the enum is used as +an external type by another crate. +#} +public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(buf) +} + +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer { + return {{ ffi_converter_name }}.lower(value) +} + +{% if !contains_object_references %} +extension {{ type_name }}: Equatable, Hashable {} +{% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift new file mode 100644 index 0000000000..786091395b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -0,0 +1,86 @@ +public enum {{ type_name }} { + + {% if e.is_flat() %} + {% for variant in e.variants() %} + // Simple error enums only carry a message + case {{ variant.name()|class_name }}(message: String) + {% endfor %} + + {%- else %} + {% for variant in e.variants() %} + case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {% endfor %} + + {%- endif %} + + fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { + return try {{ ffi_converter_name }}.lift(error) + } +} + + +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let variant: Int32 = try readInt(&buf) + switch variant { + + {% if e.is_flat() %} + + {% for variant in e.variants() %} + case {{ loop.index }}: return .{{ variant.name()|class_name }}( + message: try {{ Type::String.borrow()|read_fn }}(from: &buf) + ) + {% endfor %} + + {% else %} + + {% for variant in e.variants() %} + case {{ loop.index }}: return .{{ variant.name()|class_name }}{% if variant.has_fields() -%}( + {% for field in variant.fields() -%} + {{ field.name()|var_name }}: try {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} + {% endfor -%} + ){% endif -%} + {% endfor %} + + {% endif -%} + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + switch value { + + {% if e.is_flat() %} + + {% for variant in e.variants() %} + case .{{ variant.name()|class_name }}(_ /* message is ignored*/): + writeInt(&buf, Int32({{ loop.index }})) + {%- endfor %} + + {% else %} + + {% for variant in e.variants() %} + {% if variant.has_fields() %} + case let .{{ variant.name()|class_name }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): + writeInt(&buf, Int32({{ loop.index }})) + {% for field in variant.fields() -%} + {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) + {% endfor -%} + {% else %} + case .{{ variant.name()|class_name }}: + writeInt(&buf, Int32({{ loop.index }})) + {% endif %} + {%- endfor %} + + {%- endif %} + } + } +} + +{% if !contains_object_references %} +extension {{ type_name }}: Equatable, Hashable {} +{% endif %} +extension {{ type_name }}: Error { } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift new file mode 100644 index 0000000000..fb986beab6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float32Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterFloat: FfiConverterPrimitive { + typealias FfiType = Float + typealias SwiftType = Float + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Float { + return try lift(readFloat(&buf)) + } + + public static func write(_ value: Float, into buf: inout [UInt8]) { + writeFloat(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift new file mode 100644 index 0000000000..74421c045c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Float64Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterDouble: FfiConverterPrimitive { + typealias FfiType = Double + typealias SwiftType = Double + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double { + return try lift(readDouble(&buf)) + } + + public static func write(_ value: Double, into buf: inout [UInt8]) { + writeDouble(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift new file mode 100644 index 0000000000..167e4c7546 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift @@ -0,0 +1,69 @@ +private let UNIFFI_RUST_TASK_CALLBACK_SUCCESS: Int8 = 0 +private let UNIFFI_RUST_TASK_CALLBACK_CANCELLED: Int8 = 1 +private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS: Int8 = 0 +private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED: Int8 = 1 +private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR: Int8 = 2 + +// Encapsulates an executor that can run Rust tasks +// +// On Swift, `Task.detached` can handle this we just need to know what priority to send it. +public struct UniFfiForeignExecutor { + var priority: TaskPriority + + public init(priority: TaskPriority) { + self.priority = priority + } + + public init() { + self.priority = Task.currentPriority + } +} + +fileprivate struct FfiConverterForeignExecutor: FfiConverter { + typealias SwiftType = UniFfiForeignExecutor + // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8. + // let's use `Int`, which is equivalent to `size_t` + typealias FfiType = Int + + public static func lift(_ value: FfiType) throws -> SwiftType { + UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value))) + } + public static func lower(_ value: SwiftType) -> FfiType { + numericCast(value.priority.rawValue) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + fatalError("FfiConverterForeignExecutor.read not implemented yet") + } + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + fatalError("FfiConverterForeignExecutor.read not implemented yet") + } +} + + +fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) -> Int8 { + if let rustTask = rustTask { + let executor = try! FfiConverterForeignExecutor.lift(executorHandle) + Task.detached(priority: executor.priority) { + if delayMs != 0 { + let nanoseconds: UInt64 = numericCast(delayMs * 1000000) + try! await Task.sleep(nanoseconds: nanoseconds) + } + rustTask(taskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) + } + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } else { + // When rustTask is null, we should drop the foreign executor. + // However, since its just a value type, we don't need to do anything here. + return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS + } +} + +fileprivate func uniffiInitForeignExecutor() { + {%- match ci.ffi_foreign_executor_callback_set() %} + {%- when Some with (fn) %} + {{ fn.name() }}(uniffiForeignExecutorCallback) + {%- when None %} + {#- No foreign executor, we don't set anything #} + {% endmatch %} +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift new file mode 100644 index 0000000000..a34b128e23 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -0,0 +1,101 @@ +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_PANIC: Int8 = 2 +fileprivate let CALL_CANCELLED: Int8 = 3 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T { + try makeRustCall(callback, errorHandler: nil) +} + +private func rustCallWithError<T>( + _ errorHandler: @escaping (RustBuffer) throws -> Error, + _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall<T>( + _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, + errorHandler: ((RustBuffer) throws -> Error)? +) throws -> T { + uniffiEnsureInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> Error)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_PANIC: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try {{ Type::String.borrow()|lift_fn }}(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + case CALL_CANCELLED: + throw CancellationError() + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift new file mode 100644 index 0000000000..ac57fc5e58 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int16Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt16: FfiConverterPrimitive { + typealias FfiType = Int16 + typealias SwiftType = Int16 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int16 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int16, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift new file mode 100644 index 0000000000..0ccfc13e4e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int32Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt32: FfiConverterPrimitive { + typealias FfiType = Int32 + typealias SwiftType = Int32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int32, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift new file mode 100644 index 0000000000..d7d4082933 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int64Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt64: FfiConverterPrimitive { + typealias FfiType = Int64 + typealias SwiftType = Int64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int64, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift new file mode 100644 index 0000000000..f2387e4340 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Int8Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterInt8: FfiConverterPrimitive { + typealias FfiType = Int8 + typealias SwiftType = Int8 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int8 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Int8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift new file mode 100644 index 0000000000..05713aca26 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/MapTemplate.swift @@ -0,0 +1,22 @@ +fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for (key, value) in value { + {{ key_type|write_fn }}(key, into: &buf) + {{ value_type|write_fn }}(value, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let len: Int32 = try readInt(&buf) + var dict = {{ type_name }}() + dict.reserveCapacity(Int(len)) + for _ in 0..<len { + let key = try {{ key_type|read_fn }}(from: &buf) + let value = try {{ value_type|read_fn }}(from: &buf) + dict[key] = value + } + return dict + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap new file mode 100644 index 0000000000..f5f73ceb1b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ModuleMapTemplate.modulemap @@ -0,0 +1,6 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +module {{ config.ffi_module_name() }} { + header "{{ config.header_filename() }}" + export * +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift new file mode 100644 index 0000000000..57a77ca6df --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -0,0 +1,138 @@ +{%- let obj = ci|get_object_definition(name) %} +public protocol {{ obj.name() }}Protocol { + {% for meth in obj.methods() -%} + func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) %} {% call swift::throws(meth) -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %} -> {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + +public class {{ type_name }}: {{ obj.name() }}Protocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + {%- match obj.primary_constructor() %} + {%- when Some with (cons) %} + public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { + self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + } + {%- when None %} + {%- endmatch %} + + deinit { + try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) } + } + + {% for cons in obj.alternate_constructors() %} + + public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} { + return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + } + + {% endfor %} + + {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #} + {% for meth in obj.methods() -%} + {%- if meth.is_async() %} + + public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { + return {% call swift::try(meth) %} await uniffiRustCallAsync( + rustFutureFunc: { + {{ meth.ffi_func().name() }}( + self.pointer + {%- for arg in meth.arguments() -%} + , + {{ arg|lower_fn }}({{ arg.name()|var_name }}) + {%- endfor %} + ) + }, + pollFunc: {{ meth.ffi_rust_future_poll(ci) }}, + completeFunc: {{ meth.ffi_rust_future_complete(ci) }}, + freeFunc: {{ meth.ffi_rust_future_free(ci) }}, + {%- match meth.return_type() %} + {%- when Some(return_type) %} + liftFunc: {{ return_type|lift_fn }}, + {%- when None %} + liftFunc: { $0 }, + {%- endmatch %} + {%- match meth.throws_type() %} + {%- when Some with (e) %} + errorHandler: {{ e|ffi_converter_name }}.lift + {%- else %} + errorHandler: nil + {% endmatch %} + ) + } + + {% else -%} + + {%- match meth.return_type() -%} + + {%- when Some with (return_type) %} + + public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { + return {% call swift::try(meth) %} {{ return_type|lift_fn }}( + {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + ) + } + + {%- when None %} + + public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { + {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + } + + {%- endmatch -%} + {%- endif -%} + {% endfor %} +} + +public struct {{ ffi_converter_name }}: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = {{ type_name }} + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + return {{ type_name}}(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + return value.pointer + } +} + +{# +We always write these public functions just in case the enum is used as +an external type by another crate. +#} +public func {{ ffi_converter_name }}_lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(pointer) +} + +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + return {{ ffi_converter_name }}.lower(value) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift new file mode 100644 index 0000000000..1dac65be63 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/OptionalTemplate.swift @@ -0,0 +1,20 @@ +fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + {{ inner_type|write_fn }}(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try {{ inner_type|read_fn }}(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift new file mode 100644 index 0000000000..44de9dd358 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -0,0 +1,62 @@ +{%- let rec = ci|get_record_definition(name) %} +public struct {{ type_name }} { + {%- for field in rec.fields() %} + public var {{ field.name()|var_name }}: {{ field|type_name }} + {%- endfor %} + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init({% call swift::field_list_decl(rec) %}) { + {%- for field in rec.fields() %} + self.{{ field.name()|var_name }} = {{ field.name()|var_name }} + {%- endfor %} + } +} + +{% if !contains_object_references %} +extension {{ type_name }}: Equatable, Hashable { + public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool { + {%- for field in rec.fields() %} + if lhs.{{ field.name()|var_name }} != rhs.{{ field.name()|var_name }} { + return false + } + {%- endfor %} + return true + } + + public func hash(into hasher: inout Hasher) { + {%- for field in rec.fields() %} + hasher.combine({{ field.name()|var_name }}) + {%- endfor %} + } +} +{% endif %} + +public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + return try {{ type_name }}( + {%- for field in rec.fields() %} + {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} + {%- endfor %} + ) + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + {%- for field in rec.fields() %} + {{ field|write_fn }}(value.{{ field.name()|var_name }}, into: &buf) + {%- endfor %} + } +} + +{# +We always write these public functions just in case the struct is used as +an external type by another crate. +#} +public func {{ ffi_converter_name }}_lift(_ buf: RustBuffer) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(buf) +} + +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuffer { + return {{ ffi_converter_name }}.lower(value) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift new file mode 100644 index 0000000000..2f737b6635 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -0,0 +1,183 @@ +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer { + try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { {{ ci.ffi_rustbuffer_free().name() }}(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer<UInt8>) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt<T: FixedWidthInteger>(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset..<reader.offset + MemoryLayout<T>.size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array<UInt8> { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes<S>(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt<T: FixedWidthInteger>(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous go the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift new file mode 100644 index 0000000000..bf664f6411 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/SequenceTemplate.swift @@ -0,0 +1,21 @@ +fileprivate struct {{ ffi_converter_name }}: FfiConverterRustBuffer { + typealias SwiftType = {{ type_name }} + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + {{ inner_type|write_fn }}(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + let len: Int32 = try readInt(&buf) + var seq = {{ type_name }}() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try {{ inner_type|read_fn }}(from: &buf)) + } + return seq + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift new file mode 100644 index 0000000000..b7d3466bdd --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/StringHelper.swift @@ -0,0 +1,37 @@ +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift new file mode 100644 index 0000000000..3cd472fa0e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TimestampHelper.swift @@ -0,0 +1,34 @@ +fileprivate struct FfiConverterTimestamp: FfiConverterRustBuffer { + typealias SwiftType = Date + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date { + let seconds: Int64 = try readInt(&buf) + let nanoseconds: UInt32 = try readInt(&buf) + if seconds >= 0 { + let delta = Double(seconds) + (Double(nanoseconds) / 1.0e9) + return Date.init(timeIntervalSince1970: delta) + } else { + let delta = Double(seconds) - (Double(nanoseconds) / 1.0e9) + return Date.init(timeIntervalSince1970: delta) + } + } + + public static func write(_ value: Date, into buf: inout [UInt8]) { + var delta = value.timeIntervalSince1970 + var sign: Int64 = 1 + if delta < 0 { + // The nanoseconds portion of the epoch offset must always be + // positive, to simplify the calculation we will use the absolute + // value of the offset. + sign = -1 + delta = -delta + } + if delta.rounded(.down) > Double(Int64.max) { + fatalError("Timestamp overflow, exceeds max bounds supported by Uniffi") + } + let seconds = Int64(delta) + let nanoseconds = UInt32((delta - Double(seconds)) * 1.0e9) + writeInt(&buf, sign * seconds) + writeInt(&buf, nanoseconds) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift new file mode 100644 index 0000000000..a2c6311931 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift @@ -0,0 +1,48 @@ +{%- if func.is_async() %} + +public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { + return {% call swift::try(func) %} await uniffiRustCallAsync( + rustFutureFunc: { + {{ func.ffi_func().name() }}( + {%- for arg in func.arguments() %} + {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) + }, + pollFunc: {{ func.ffi_rust_future_poll(ci) }}, + completeFunc: {{ func.ffi_rust_future_complete(ci) }}, + freeFunc: {{ func.ffi_rust_future_free(ci) }}, + {%- match func.return_type() %} + {%- when Some(return_type) %} + liftFunc: {{ return_type|lift_fn }}, + {%- when None %} + liftFunc: { $0 }, + {%- endmatch %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + errorHandler: {{ e|ffi_converter_name }}.lift + {%- else %} + errorHandler: nil + {% endmatch %} + ) +} + +{% else %} + +{%- match func.return_type() -%} +{%- when Some with (return_type) %} + +public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} { + return {% call swift::try(func) %} {{ return_type|lift_fn }}( + {% call swift::to_ffi_call(func) %} + ) +} + +{%- when None %} + +public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} { + {% call swift::to_ffi_call(func) %} +} + +{% endmatch %} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift new file mode 100644 index 0000000000..aba34f4b0b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -0,0 +1,98 @@ +{%- import "macros.swift" as swift %} +{%- for type_ in ci.iter_types() %} +{%- let type_name = type_|type_name %} +{%- let ffi_converter_name = type_|ffi_converter_name %} +{%- let canonical_type_name = type_|canonical_name %} +{%- let contains_object_references = ci.item_contains_object_references(type_) %} + +{# + # Map `Type` instances to an include statement for that type. + # + # There is a companion match in `KotlinCodeOracle::create_code_type()` which performs a similar function for the + # Rust code. + # + # - When adding additional types here, make sure to also add a match arm to that function. + # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches + #} +{%- match type_ %} + +{%- when Type::Boolean %} +{%- include "BooleanHelper.swift" %} + +{%- when Type::String %} +{%- include "StringHelper.swift" %} + +{%- when Type::Bytes %} +{%- include "DataHelper.swift" %} + +{%- when Type::Int8 %} +{%- include "Int8Helper.swift" %} + +{%- when Type::Int16 %} +{%- include "Int16Helper.swift" %} + +{%- when Type::Int32 %} +{%- include "Int32Helper.swift" %} + +{%- when Type::Int64 %} +{%- include "Int64Helper.swift" %} + +{%- when Type::UInt8 %} +{%- include "UInt8Helper.swift" %} + +{%- when Type::UInt16 %} +{%- include "UInt16Helper.swift" %} + +{%- when Type::UInt32 %} +{%- include "UInt32Helper.swift" %} + +{%- when Type::UInt64 %} +{%- include "UInt64Helper.swift" %} + +{%- when Type::Float32 %} +{%- include "Float32Helper.swift" %} + +{%- when Type::Float64 %} +{%- include "Float64Helper.swift" %} + +{%- when Type::Timestamp %} +{%- include "TimestampHelper.swift" %} + +{%- when Type::Duration %} +{%- include "DurationHelper.swift" %} + +{%- when Type::CallbackInterface { name, module_path } %} +{%- include "CallbackInterfaceTemplate.swift" %} + +{%- when Type::ForeignExecutor %} +{%- include "ForeignExecutorTemplate.swift" %} + +{%- when Type::Custom { name, module_path, builtin } %} +{%- include "CustomType.swift" %} + +{%- when Type::Enum { name, module_path } %} +{%- let e = ci.get_enum_definition(name).unwrap() %} +{%- if ci.is_name_used_as_error(name) %} +{%- include "ErrorTemplate.swift" %} +{%- else %} +{%- include "EnumTemplate.swift" %} +{% endif %} + +{%- when Type::Object{ name, module_path, imp } %} +{%- include "ObjectTemplate.swift" %} + +{%- when Type::Record { name, module_path } %} +{%- include "RecordTemplate.swift" %} + +{%- when Type::Optional { inner_type } %} +{%- include "OptionalTemplate.swift" %} + +{%- when Type::Sequence { inner_type } %} +{%- include "SequenceTemplate.swift" %} + +{%- when Type::Map { key_type, value_type } %} +{%- include "MapTemplate.swift" %} + +{%- else %} +{%- endmatch %} +{%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift new file mode 100644 index 0000000000..b7fc0942a5 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt16Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt16: FfiConverterPrimitive { + typealias FfiType = UInt16 + typealias SwiftType = UInt16 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt16 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift new file mode 100644 index 0000000000..e7a64aab93 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt32Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt32: FfiConverterPrimitive { + typealias FfiType = UInt32 + typealias SwiftType = UInt32 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift new file mode 100644 index 0000000000..eb674a2c53 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt64Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt64: FfiConverterPrimitive { + typealias FfiType = UInt64 + typealias SwiftType = UInt64 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt64 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift new file mode 100644 index 0000000000..4baf613494 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/UInt8Helper.swift @@ -0,0 +1,12 @@ +fileprivate struct FfiConverterUInt8: FfiConverterPrimitive { + typealias FfiType = UInt8 + typealias SwiftType = UInt8 + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt8 { + return try lift(readInt(&buf)) + } + + public static func write(_ value: UInt8, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift new file mode 100644 index 0000000000..0a125e6f61 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -0,0 +1,89 @@ +{# +// Template to call into rust. Used in several places. +// Variable names in `arg_list_decl` should match up with arg lists +// passed to rust via `arg_list_lowered` +#} + +{%- macro to_ffi_call(func) -%} + {%- call try(func) -%} + {%- match func.throws_type() -%} + {%- when Some with (e) -%} + rustCallWithError({{ e|ffi_converter_name }}.lift) { + {%- else -%} + rustCall() { + {%- endmatch %} + {{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} $0) +} +{%- endmacro -%} + +{%- macro to_ffi_call_with_prefix(prefix, func) -%} +{% call try(func) %} + {%- match func.throws_type() %} + {%- when Some with (e) %} + rustCallWithError({{ e|ffi_converter_name }}.lift) { + {%- else %} + rustCall() { + {% endmatch %} + {{ func.ffi_func().name() }}( + {{- prefix }}, {% call arg_list_lowered(func) -%} $0 + ) +} +{%- endmacro %} + +{%- macro arg_list_lowered(func) %} + {%- for arg in func.arguments() %} + {{ arg|lower_fn }}({{ arg.name()|var_name }}), + {%- endfor %} +{%- endmacro -%} + +{#- +// Arglist as used in Swift declarations of methods, functions and constructors. +// Note the var_name and type_name filters. +-#} + +{% macro arg_list_decl(func) %} + {%- for arg in func.arguments() -%} + {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}} + {%- match arg.default_value() %} + {%- when Some with(literal) %} = {{ literal|literal_swift(arg) }} + {%- else %} + {%- endmatch %} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + +{#- +// Field lists as used in Swift declarations of Records and Enums. +// Note the var_name and type_name filters. +-#} +{% macro field_list_decl(item) %} + {%- for field in item.fields() -%} + {{ field.name()|var_name }}: {{ field|type_name -}} + {%- match field.default_value() %} + {%- when Some with(literal) %} = {{ literal|literal_swift(field) }} + {%- else %} + {%- endmatch -%} + {% if !loop.last %}, {% endif %} + {%- endfor %} +{%- endmacro %} + + +{% macro arg_list_protocol(func) %} + {%- for arg in func.arguments() -%} + {% if config.omit_argument_labels() %}_ {% endif %}{{ arg.name()|var_name }}: {{ arg|type_name -}} + {%- if !loop.last %}, {% endif -%} + {%- endfor %} +{%- endmacro %} + + +{%- macro async(func) %} +{%- if func.is_async() %}async{% endif %} +{%- endmacro -%} + +{%- macro throws(func) %} +{%- if func.throws() %}throws{% endif %} +{%- endmacro -%} + +{%- macro try(func) %} +{%- if func.throws() %}try {% else %}try! {% endif %} +{%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift new file mode 100644 index 0000000000..c34d348efb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift @@ -0,0 +1,68 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +{%- import "macros.swift" as swift %} +import Foundation +{%- for imported_class in self.imports() %} +import {{ imported_class }} +{%- endfor %} + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport({{ config.ffi_module_name() }}) +import {{ config.ffi_module_name() }} +#endif + +{% include "RustBufferTemplate.swift" %} +{% include "Helpers.swift" %} + +// Public interface members begin here. +{{ type_helper_code }} + +{%- if ci.has_async_fns() %} +{% include "Async.swift" %} +{%- endif %} + +{%- for func in ci.function_definitions() %} +{%- include "TopLevelFunctionTemplate.swift" %} +{%- endfor %} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variables to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private var initializationResult: InitializationResult { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = {{ ci.uniffi_contract_version() }} + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = {{ ci.ffi_uniffi_contract_version().name() }}() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + + {%- for (name, expected_checksum) in ci.iter_checksums() %} + if ({{ name }}() != {{ expected_checksum }}) { + return InitializationResult.apiChecksumMismatch + } + {%- endfor %} + + {% for fn in self.initialization_fns() -%} + {{ fn }}() + {% endfor -%} + + return InitializationResult.ok +} + +private func uniffiEnsureInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs new file mode 100644 index 0000000000..c3b2f15277 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs @@ -0,0 +1,202 @@ +/* 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/. */ + +use crate::{ + bindings::{RunScriptOptions, TargetLanguage}, + library_mode::generate_bindings, +}; +use anyhow::{bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +use std::ffi::OsStr; +use std::fs::{read_to_string, File}; +use std::io::Write; +use std::process::{Command, Stdio}; +use uniffi_testing::UniFFITestHelper; + +/// Run Swift tests for a UniFFI test fixture +pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { + run_script( + tmp_dir, + fixture_name, + script_file, + vec![], + &RunScriptOptions::default(), + ) +} + +/// Run a Swift script +/// +/// This function will set things up so that the script can import the UniFFI bindings for a crate +pub fn run_script( + tmp_dir: &str, + crate_name: &str, + script_file: &str, + args: Vec<String>, + options: &RunScriptOptions, +) -> Result<()> { + let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let test_helper = UniFFITestHelper::new(crate_name)?; + let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; + let generated_sources = GeneratedSources::new(crate_name, &cdylib_path, &out_dir)?; + + // Compile the generated sources together to create a single swift module + compile_swift_module( + &out_dir, + &generated_sources.main_module, + &generated_sources.generated_swift_files, + &generated_sources.module_map, + options, + )?; + + // Run the test script against compiled bindings + let mut command = create_command("swift", options); + command + .current_dir(&out_dir) + .arg("-I") + .arg(&out_dir) + .arg("-L") + .arg(&out_dir) + .args(calc_library_args(&out_dir)?) + .arg("-Xcc") + .arg(format!( + "-fmodule-map-file={}", + generated_sources.module_map + )) + .arg(&script_path) + .args(args); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when running test script")? + .wait() + .context("Failed to wait for `swiftc` when running test script")?; + if !status.success() { + bail!("running `swift` to run test script failed ({:?})", command) + } + Ok(()) +} + +fn compile_swift_module<T: AsRef<OsStr>>( + out_dir: &Utf8Path, + module_name: &str, + sources: impl IntoIterator<Item = T>, + module_map: &Utf8Path, + options: &RunScriptOptions, +) -> Result<()> { + let output_filename = format!("{DLL_PREFIX}testmod_{module_name}{DLL_SUFFIX}"); + let mut command = create_command("swiftc", options); + command + .current_dir(out_dir) + .arg("-emit-module") + .arg("-module-name") + .arg(module_name) + .arg("-o") + .arg(output_filename) + .arg("-emit-library") + .arg("-Xcc") + .arg(format!("-fmodule-map-file={module_map}")) + .arg("-I") + .arg(out_dir) + .arg("-L") + .arg(out_dir) + .args(calc_library_args(out_dir)?) + .args(sources); + let status = command + .spawn() + .context("Failed to spawn `swiftc` when compiling bindings")? + .wait() + .context("Failed to wait for `swiftc` when compiling bindings")?; + if !status.success() { + bail!( + "running `swiftc` to compile bindings failed ({:?})", + command + ) + }; + Ok(()) +} + +// Stores sources generated by `uniffi-bindgen-swift` +struct GeneratedSources { + main_module: String, + generated_swift_files: Vec<Utf8PathBuf>, + module_map: Utf8PathBuf, +} + +impl GeneratedSources { + fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result<Self> { + let sources = + generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?; + let main_source = sources + .iter() + .find(|s| s.package.name == crate_name) + .unwrap(); + let main_module = main_source.config.bindings.swift.module_name(); + let modulemap_glob = glob(&out_dir.join("*.modulemap"))?; + let module_map = match modulemap_glob.len() { + 0 => bail!("No modulemap files found in {out_dir}"), + // Normally we only generate 1 module map and can return it directly + 1 => modulemap_glob.into_iter().next().unwrap(), + // When we use multiple UDL files in a test, for example the ext-types fixture, + // then we get multiple module maps and need to combine them + _ => { + let path = out_dir.join("combined.modulemap"); + let mut f = File::create(&path)?; + write!( + f, + "{}", + modulemap_glob + .into_iter() + .map(|path| Ok(read_to_string(path)?)) + .collect::<Result<Vec<String>>>()? + .join("\n") + )?; + path + } + }; + + Ok(GeneratedSources { + main_module, + generated_swift_files: glob(&out_dir.join("*.swift"))?, + module_map, + }) + } +} + +fn create_command(program: &str, options: &RunScriptOptions) -> Command { + let mut command = Command::new(program); + if !options.show_compiler_messages { + // This prevents most compiler messages, but not remarks + command.arg("-suppress-warnings"); + // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument, + // maybe we can eventually move to that + command.stderr(Stdio::null()); + } + command +} + +// Wraps glob to use Utf8Paths and flattens errors +fn glob(globspec: &Utf8Path) -> Result<Vec<Utf8PathBuf>> { + glob::glob(globspec.as_str())? + .map(|globresult| Ok(Utf8PathBuf::try_from(globresult?)?)) + .collect() +} + +fn calc_library_args(out_dir: &Utf8Path) -> Result<Vec<String>> { + let results = glob::glob(out_dir.join(format!("{DLL_PREFIX}*{DLL_SUFFIX}")).as_str())?; + results + .map(|globresult| { + let path = Utf8PathBuf::try_from(globresult.unwrap())?; + Ok(format!( + "-l{}", + path.file_name() + .unwrap() + .strip_prefix(DLL_PREFIX) + .unwrap() + .strip_suffix(DLL_SUFFIX) + .unwrap() + )) + }) + .collect() +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs new file mode 100644 index 0000000000..e3bca4f966 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs @@ -0,0 +1,149 @@ +/* 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/. */ + +//! # Callback Interface definitions for a `ComponentInterface`. +//! +//! This module converts callback interface definitions from UDL into structures that +//! can be added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! callback interface Example { +//! string hello(); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`CallbackInterface`] member being added to the resulting +//! [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # callback interface Example { +//! # string hello(); +//! # }; +//! # "##, "crate_name")?; +//! let callback = ci.get_callback_interface_definition("Example").unwrap(); +//! assert_eq!(callback.name(), "Example"); +//! assert_eq!(callback.methods()[0].name(), "hello"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use uniffi_meta::Checksum; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::object::Method; +use super::{AsType, Type, TypeIterator}; + +#[derive(Debug, Clone, Checksum)] +pub struct CallbackInterface { + pub(super) name: String, + pub(super) module_path: String, + pub(super) methods: Vec<Method>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_init_callback: FfiFunction, +} + +impl CallbackInterface { + pub fn new(name: String, module_path: String) -> CallbackInterface { + CallbackInterface { + name, + module_path, + methods: Default::default(), + ffi_init_callback: Default::default(), + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn methods(&self) -> Vec<&Method> { + self.methods.iter().collect() + } + + pub fn ffi_init_callback(&self) -> &FfiFunction { + &self.ffi_init_callback + } + + pub(super) fn derive_ffi_funcs(&mut self) { + self.ffi_init_callback.name = + uniffi_meta::init_callback_fn_symbol_name(&self.module_path, &self.name); + self.ffi_init_callback.arguments = vec![FfiArgument { + name: "callback_stub".to_string(), + type_: FfiType::ForeignCallback, + }]; + self.ffi_init_callback.return_type = None; + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.methods.iter().flat_map(Method::iter_types)) + } +} + +impl AsType for CallbackInterface { + fn as_type(&self) -> Type { + Type::CallbackInterface { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + + #[test] + fn test_empty_interface() { + const UDL: &str = r#" + namespace test{}; + // Weird, but allowed. + callback interface Testing {}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.callback_interface_definitions().len(), 1); + assert_eq!( + ci.get_callback_interface_definition("Testing") + .unwrap() + .methods() + .len(), + 0 + ); + } + + #[test] + fn test_multiple_interfaces() { + const UDL: &str = r#" + namespace test{}; + callback interface One { + void one(); + }; + callback interface Two { + u32 two(); + u64 too(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.callback_interface_definitions().len(), 2); + + let callbacks_one = ci.get_callback_interface_definition("One").unwrap(); + assert_eq!(callbacks_one.methods().len(), 1); + assert_eq!(callbacks_one.methods()[0].name(), "one"); + + let callbacks_two = ci.get_callback_interface_definition("Two").unwrap(); + assert_eq!(callbacks_two.methods().len(), 2); + assert_eq!(callbacks_two.methods()[0].name(), "two"); + assert_eq!(callbacks_two.methods()[1].name(), "too"); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs new file mode 100644 index 0000000000..82baf1dd50 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs @@ -0,0 +1,567 @@ +/* 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/. */ + +//! # Enum definitions for a `ComponentInterface`. +//! +//! This module converts enum definition from UDL into structures that can be +//! added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Enum`] member being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##, "crate_name")?; +//! let e = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(e.name(), "Example"); +//! assert_eq!(e.variants().len(), 2); +//! assert_eq!(e.variants()[0].name(), "one"); +//! assert_eq!(e.variants()[1].name(), "two"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Like in Rust, UniFFI enums can contain associated data, but this needs to be +//! declared with a different syntax in order to work within the restrictions of +//! WebIDL. A declaration like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Enum] +//! interface Example { +//! Zero(); +//! One(u32 first); +//! Two(u32 first, string second); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member whose variants have associated fields: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Enum] +//! # interface ExampleWithData { +//! # Zero(); +//! # One(u32 first); +//! # Two(u32 first, string second); +//! # }; +//! # "##, "crate_name")?; +//! let e = ci.get_enum_definition("ExampleWithData").unwrap(); +//! assert_eq!(e.name(), "ExampleWithData"); +//! assert_eq!(e.variants().len(), 3); +//! assert_eq!(e.variants()[0].name(), "Zero"); +//! assert_eq!(e.variants()[0].fields().len(), 0); +//! assert_eq!(e.variants()[1].name(), "One"); +//! assert_eq!(e.variants()[1].fields().len(), 1); +//! assert_eq!(e.variants()[1].fields()[0].name(), "first"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! # Enums are also used to represent error definitions for a `ComponentInterface`. +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Error] +//! enum Example { +//! "one", +//! "two" +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member with fieldless variants being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # enum Example { +//! # "one", +//! # "two" +//! # }; +//! # "##, "crate_name")?; +//! let err = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(err.name(), "Example"); +//! assert_eq!(err.variants().len(), 2); +//! assert_eq!(err.variants()[0].name(), "one"); +//! assert_eq!(err.variants()[1].name(), "two"); +//! assert_eq!(err.is_flat(), true); +//! assert!(ci.is_name_used_as_error(&err.name())); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! [Error] +//! interface Example { +//! one(i16 code); +//! two(string reason); +//! three(i32 x, i32 y); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Enum`] member with variants that have fields being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # [Error] +//! # interface Example { +//! # one(); +//! # two(string reason); +//! # three(i32 x, i32 y); +//! # }; +//! # "##, "crate_name")?; +//! let err = ci.get_enum_definition("Example").unwrap(); +//! assert_eq!(err.name(), "Example"); +//! assert_eq!(err.variants().len(), 3); +//! assert_eq!(err.variants()[0].name(), "one"); +//! assert_eq!(err.variants()[1].name(), "two"); +//! assert_eq!(err.variants()[2].name(), "three"); +//! assert_eq!(err.variants()[0].fields().len(), 0); +//! assert_eq!(err.variants()[1].fields().len(), 1); +//! assert_eq!(err.variants()[1].fields()[0].name(), "reason"); +//! assert_eq!(err.variants()[2].fields().len(), 2); +//! assert_eq!(err.variants()[2].fields()[0].name(), "x"); +//! assert_eq!(err.variants()[2].fields()[1].name(), "y"); +//! assert_eq!(err.is_flat(), false); +//! assert!(ci.is_name_used_as_error(err.name())); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::record::Field; +use super::{AsType, Type, TypeIterator}; + +/// Represents an enum with named variants, each of which may have named +/// and typed fields. +/// +/// Enums are passed across the FFI by serializing to a bytebuffer, with a +/// i32 indicating the variant followed by the serialization of each field. +#[derive(Debug, Clone, PartialEq, Eq, Checksum)] +pub struct Enum { + pub(super) name: String, + pub(super) module_path: String, + pub(super) variants: Vec<Variant>, + // NOTE: `flat` is a misleading name and to make matters worse, has 2 different + // meanings depending on the context :( + // * When used as part of Rust scaffolding generation, it means "is this enum + // used with an Error, and that error should we lowered to foreign bindings + // by converting each variant to a string and lowering the variant with that + // string?". In that context, it should probably be called `lowered_as_string` or + // similar. + // * When used as part of bindings generation, it means "does this enum have only + // variants with no associated data"? The foreign binding generators are likely + // to generate significantly different versions of the enum based on that value. + // + // The reason it is described as "has 2 different meanings" by way of example: + // * For an Enum described as being a flat error, but the enum itself has variants with data, + // `flat` will be `true` for the Enum when generating scaffolding and `false` when + // generating bindings. + // * For an Enum not used as an error but which has no variants with data, `flat` will be + // false when generating the scaffolding but `true` when generating bindings. + pub(super) flat: bool, +} + +impl Enum { + pub fn name(&self) -> &str { + &self.name + } + + pub fn variants(&self) -> &[Variant] { + &self.variants + } + + pub fn is_flat(&self) -> bool { + self.flat + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.variants.iter().flat_map(Variant::iter_types)) + } + + // Sadly can't use TryFrom due to the 'is_flat' complication. + pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result<Self> { + // This is messy - error enums are considered "flat" if the user + // opted in via a special attribute, regardless of whether the enum + // is actually flat. + // Real enums are considered flat iff they are actually flat. + // We don't have that context here, so this is handled by our caller. + Ok(Self { + name: meta.name, + module_path: meta.module_path, + variants: meta + .variants + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_>>()?, + flat, + }) + } +} + +impl AsType for Enum { + fn as_type(&self) -> Type { + Type::Enum { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +/// Represents an individual variant in an Enum. +/// +/// Each variant has a name and zero or more fields. +#[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)] +pub struct Variant { + pub(super) name: String, + pub(super) fields: Vec<Field>, +} + +impl Variant { + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn has_fields(&self) -> bool { + !self.fields.is_empty() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.fields.iter().flat_map(Field::iter_types)) + } +} + +impl TryFrom<uniffi_meta::VariantMetadata> for Variant { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::VariantMetadata) -> Result<Self> { + Ok(Self { + name: meta.name, + fields: meta + .fields + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_>>()?, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::{ComponentInterface, FfiType}; + use super::*; + + #[test] + fn test_duplicate_variants() { + const UDL: &str = r#" + namespace test{}; + // Weird, but currently allowed! + // We should probably disallow this... + enum Testing { "one", "two", "one" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants().len(), + 3 + ); + } + + #[test] + fn test_associated_data() { + const UDL: &str = r#" + namespace test { + void takes_an_enum(TestEnum e); + void takes_an_enum_with_data(TestEnumWithData ed); + TestEnum returns_an_enum(); + TestEnumWithData returns_an_enum_with_data(); + }; + + enum TestEnum { "one", "two" }; + + [Enum] + interface TestEnumWithData { + Zero(); + One(u32 first); + Two(u32 first, string second); + }; + + [Enum] + interface TestEnumWithoutData { + One(); + Two(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 3); + assert_eq!(ci.function_definitions().len(), 4); + + // The "flat" enum with no associated data. + let e = ci.get_enum_definition("TestEnum").unwrap(); + assert!(e.is_flat()); + assert_eq!(e.variants().len(), 2); + assert_eq!( + e.variants().iter().map(|v| v.name()).collect::<Vec<_>>(), + vec!["one", "two"] + ); + assert_eq!(e.variants()[0].fields().len(), 0); + assert_eq!(e.variants()[1].fields().len(), 0); + + // The enum with associated data. + let ed = ci.get_enum_definition("TestEnumWithData").unwrap(); + assert!(!ed.is_flat()); + assert_eq!(ed.variants().len(), 3); + assert_eq!( + ed.variants().iter().map(|v| v.name()).collect::<Vec<_>>(), + vec!["Zero", "One", "Two"] + ); + assert_eq!(ed.variants()[0].fields().len(), 0); + assert_eq!( + ed.variants()[1] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first"] + ); + assert_eq!( + ed.variants()[1] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::UInt32] + ); + assert_eq!( + ed.variants()[2] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first", "second"] + ); + assert_eq!( + ed.variants()[2] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::UInt32, Type::String] + ); + + // The enum declared via interface, but with no associated data. + let ewd = ci.get_enum_definition("TestEnumWithoutData").unwrap(); + assert_eq!(ewd.variants().len(), 2); + assert_eq!( + ewd.variants().iter().map(|v| v.name()).collect::<Vec<_>>(), + vec!["One", "Two"] + ); + assert_eq!(ewd.variants()[0].fields().len(), 0); + assert_eq!(ewd.variants()[1].fields().len(), 0); + + // Flat enums pass over the FFI as bytebuffers. + // (It might be nice to optimize these to pass as plain integers, but that's + // difficult atop the current factoring of `ComponentInterface` and friends). + let farg = ci.get_function_definition("takes_an_enum").unwrap(); + assert_eq!( + farg.arguments()[0].as_type(), + Type::Enum { + name: "TestEnum".into(), + module_path: "crate_name".into() + } + ); + assert_eq!( + farg.ffi_func().arguments()[0].type_(), + FfiType::RustBuffer(None) + ); + let fret = ci.get_function_definition("returns_an_enum").unwrap(); + assert!( + matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnum" && !ci.is_name_used_as_error(name)) + ); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + + // Enums with associated data pass over the FFI as bytebuffers. + let farg = ci + .get_function_definition("takes_an_enum_with_data") + .unwrap(); + assert_eq!( + farg.arguments()[0].as_type(), + Type::Enum { + name: "TestEnumWithData".into(), + module_path: "crate_name".into() + } + ); + assert_eq!( + farg.ffi_func().arguments()[0].type_(), + FfiType::RustBuffer(None) + ); + let fret = ci + .get_function_definition("returns_an_enum_with_data") + .unwrap(); + assert!( + matches!(fret.return_type(), Some(Type::Enum { name, .. }) if name == "TestEnumWithData" && !ci.is_name_used_as_error(name)) + ); + assert!(matches!( + fret.ffi_func().return_type(), + Some(FfiType::RustBuffer(None)) + )); + } + + // Tests for [Error], which are represented as `Enum` + #[test] + fn test_variants() { + const UDL: &str = r#" + namespace test{}; + [Error] + enum Testing { "one", "two", "three" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let error = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<&str>>(), + vec!("one", "two", "three") + ); + assert!(error.is_flat()); + assert!(ci.is_name_used_as_error(&error.name)); + } + + #[test] + fn test_duplicate_error_variants() { + const UDL: &str = r#" + namespace test{}; + // Weird, but currently allowed! + // We should probably disallow this... + [Error] + enum Testing { "one", "two", "one" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants().len(), + 3 + ); + } + + #[test] + fn test_variant_data() { + const UDL: &str = r#" + namespace test{}; + + [Error] + interface Testing { + One(string reason); + Two(u8 code); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let error: &Enum = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + error + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<&str>>(), + vec!("One", "Two") + ); + assert!(!error.is_flat()); + assert!(ci.is_name_used_as_error(&error.name)); + } + + #[test] + fn test_enum_variant_named_error() { + const UDL: &str = r#" + namespace test{}; + + [Enum] + interface Testing { + Normal(string first); + Error(string first); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.enum_definitions().count(), 1); + let testing: &Enum = ci.get_enum_definition("Testing").unwrap(); + assert_eq!( + testing.variants()[0] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first"] + ); + assert_eq!( + testing.variants()[0] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::String] + ); + assert_eq!( + testing.variants()[1] + .fields() + .iter() + .map(|f| f.name()) + .collect::<Vec<_>>(), + vec!["first"] + ); + assert_eq!( + testing.variants()[1] + .fields() + .iter() + .map(|f| f.as_type()) + .collect::<Vec<_>>(), + vec![Type::String] + ); + assert_eq!( + testing + .variants() + .iter() + .map(|v| v.name()) + .collect::<Vec<_>>(), + vec!["Normal", "Error"] + ); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs new file mode 100644 index 0000000000..d18aaf8262 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs @@ -0,0 +1,227 @@ +/* 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/. */ + +//! # Low-level typesystem for the FFI layer of a component interface. +//! +//! This module provides the "FFI-level" typesystem of a UniFFI Rust Component, that is, +//! the C-style functions and structs and primitive datatypes that are used to interface +//! between the Rust component code and the foreign-language bindings. +//! +//! These types are purely an implementation detail of UniFFI, so consumers shouldn't +//! need to know about them. But as a developer working on UniFFI itself, you're likely +//! to spend a lot of time thinking about how these low-level types are used to represent +//! the higher-level "interface types" from the [`Type`] enum. +/// Represents the restricted set of low-level types that can be used to construct +/// the C-style FFI layer between a rust component and its foreign language bindings. +/// +/// For the types that involve memory allocation, we make a distinction between +/// "owned" types (the recipient must free it, or pass it to someone else) and +/// "borrowed" types (the sender must keep it alive for the duration of the call). +use uniffi_meta::{ExternalKind, Type}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum FfiType { + // N.B. there are no booleans at this layer, since they cause problems for JNA. + UInt8, + Int8, + UInt16, + Int16, + UInt32, + Int32, + UInt64, + Int64, + Float32, + Float64, + /// A `*const c_void` pointer to a rust-owned `Arc<T>`. + /// If you've got one of these, you must call the appropriate rust function to free it. + /// The templates will generate a unique `free` function for each T. + /// The inner string references the name of the `T` type. + RustArcPtr(String), + /// A byte buffer allocated by rust, and owned by whoever currently holds it. + /// If you've got one of these, you must either call the appropriate rust function to free it + /// or pass it to someone that will. + /// If the inner option is Some, it is the name of the external type. The bindings may need + /// to use this name to import the correct RustBuffer for that type. + RustBuffer(Option<String>), + /// A borrowed reference to some raw bytes owned by foreign language code. + /// The provider of this reference must keep it alive for the duration of the receiving call. + ForeignBytes, + /// Pointer to a callback function that handles all callbacks on the foreign language side. + ForeignCallback, + /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can + /// either use an actual pointer or a usized integer. + ForeignExecutorHandle, + /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor + ForeignExecutorCallback, + /// Pointer to a Rust future + RustFutureHandle, + /// Continuation function for a Rust future + RustFutureContinuationCallback, + RustFutureContinuationData, + // TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something. + // We don't need that yet and it's possible we never will, so it isn't here for now. +} + +/// When passing data across the FFI, each `Type` value will be lowered into a corresponding +/// `FfiType` value. This conversion tells you which one. +/// +/// Note that the conversion is one-way - given an FfiType, it is not in general possible to +/// tell what the corresponding Type is that it's being used to represent. +impl From<&Type> for FfiType { + fn from(t: &Type) -> FfiType { + match t { + // Types that are the same map to themselves, naturally. + Type::UInt8 => FfiType::UInt8, + Type::Int8 => FfiType::Int8, + Type::UInt16 => FfiType::UInt16, + Type::Int16 => FfiType::Int16, + Type::UInt32 => FfiType::UInt32, + Type::Int32 => FfiType::Int32, + Type::UInt64 => FfiType::UInt64, + Type::Int64 => FfiType::Int64, + Type::Float32 => FfiType::Float32, + Type::Float64 => FfiType::Float64, + // Booleans lower into an Int8, to work around a bug in JNA. + Type::Boolean => FfiType::Int8, + // Strings are always owned rust values. + // We might add a separate type for borrowed strings in future. + Type::String => FfiType::RustBuffer(None), + // Byte strings are also always owned rust values. + // We might add a separate type for borrowed byte strings in future as well. + Type::Bytes => FfiType::RustBuffer(None), + // Objects are pointers to an Arc<> + Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()), + // Callback interfaces are passed as opaque integer handles. + Type::CallbackInterface { .. } => FfiType::UInt64, + Type::ForeignExecutor => FfiType::ForeignExecutorHandle, + // Other types are serialized into a bytebuffer and deserialized on the other side. + Type::Enum { .. } + | Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Map { .. } + | Type::Timestamp + | Type::Duration => FfiType::RustBuffer(None), + Type::External { + name, + kind: ExternalKind::Interface, + .. + } => FfiType::RustArcPtr(name.clone()), + Type::External { + name, + kind: ExternalKind::DataClass, + .. + } => FfiType::RustBuffer(Some(name.clone())), + Type::Custom { builtin, .. } => FfiType::from(builtin.as_ref()), + } + } +} + +// Needed for rust scaffolding askama template +impl From<Type> for FfiType { + fn from(ty: Type) -> Self { + (&ty).into() + } +} + +impl From<&&Type> for FfiType { + fn from(ty: &&Type) -> Self { + (*ty).into() + } +} + +/// Represents an "extern C"-style function that will be part of the FFI. +/// +/// These can't be declared explicitly in the UDL, but rather, are derived automatically +/// from the high-level interface. Each callable thing in the component API will have a +/// corresponding `FfiFunction` through which it can be invoked, and UniFFI also provides +/// some built-in `FfiFunction` helpers for use in the foreign language bindings. +#[derive(Debug, Clone)] +pub struct FfiFunction { + pub(super) name: String, + pub(super) is_async: bool, + pub(super) arguments: Vec<FfiArgument>, + pub(super) return_type: Option<FfiType>, + pub(super) has_rust_call_status_arg: bool, + /// Used by C# generator to differentiate the free function and call it with void* + /// instead of C# `SafeHandle` type. See <https://github.com/mozilla/uniffi-rs/pull/1488>. + pub(super) is_object_free_function: bool, +} + +impl FfiFunction { + pub fn name(&self) -> &str { + &self.name + } + + pub fn is_async(&self) -> bool { + self.is_async + } + + pub fn arguments(&self) -> Vec<&FfiArgument> { + self.arguments.iter().collect() + } + + pub fn return_type(&self) -> Option<&FfiType> { + self.return_type.as_ref() + } + + pub fn has_rust_call_status_arg(&self) -> bool { + self.has_rust_call_status_arg + } + + pub fn is_object_free_function(&self) -> bool { + self.is_object_free_function + } + + pub fn init( + &mut self, + return_type: Option<FfiType>, + args: impl IntoIterator<Item = FfiArgument>, + ) { + self.arguments = args.into_iter().collect(); + if self.is_async() { + self.return_type = Some(FfiType::RustFutureHandle); + self.has_rust_call_status_arg = false; + } else { + self.return_type = return_type; + } + } +} + +impl Default for FfiFunction { + fn default() -> Self { + Self { + name: "".into(), + is_async: false, + arguments: Vec::new(), + return_type: None, + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } +} + +/// Represents an argument to an FFI function. +/// +/// Each argument has a name and a type. +#[derive(Debug, Clone)] +pub struct FfiArgument { + pub(super) name: String, + pub(super) type_: FfiType, +} + +impl FfiArgument { + pub fn name(&self) -> &str { + &self.name + } + pub fn type_(&self) -> FfiType { + self.type_.clone() + } +} + +#[cfg(test)] +mod test { + // There's not really much to test here to be honest, + // it's mostly type declarations. +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/function.rs b/third_party/rust/uniffi_bindgen/src/interface/function.rs new file mode 100644 index 0000000000..2d18288c1c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs @@ -0,0 +1,367 @@ +/* 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/. */ + +//! # Function definitions for a `ComponentInterface`. +//! +//! This module converts function definitions from UDL into structures that +//! can be added to a `ComponentInterface`. A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! namespace example { +//! string hello(); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Function`] member being added to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # use uniffi_bindgen::interface::Type; +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example { +//! # string hello(); +//! # }; +//! # "##, "crate_name")?; +//! let func = ci.get_function_definition("hello").unwrap(); +//! assert_eq!(func.name(), "hello"); +//! assert!(matches!(func.return_type(), Some(Type::String))); +//! assert_eq!(func.arguments().len(), 0); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::{AsType, ComponentInterface, Literal, ObjectImpl, Type, TypeIterator}; +use uniffi_meta::Checksum; + +/// Represents a standalone function. +/// +/// Each `Function` corresponds to a standalone function in the rust module, +/// and has a corresponding standalone function in the foreign language bindings. +/// +/// In the FFI, this will be a standalone function with appropriately lowered types. +#[derive(Debug, Clone, Checksum)] +pub struct Function { + pub(super) name: String, + pub(super) module_path: String, + pub(super) is_async: bool, + pub(super) arguments: Vec<Argument>, + pub(super) return_type: Option<Type>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func: FfiFunction, + pub(super) throws: Option<Type>, + pub(super) checksum_fn_name: String, + // Force a checksum value, or we'll fallback to the trait. + #[checksum_ignore] + pub(super) checksum: Option<u16>, +} + +impl Function { + pub fn name(&self) -> &str { + &self.name + } + + pub fn is_async(&self) -> bool { + self.is_async + } + + pub fn arguments(&self) -> Vec<&Argument> { + self.arguments.iter().collect() + } + + pub fn full_arguments(&self) -> Vec<Argument> { + self.arguments.to_vec() + } + + pub fn return_type(&self) -> Option<&Type> { + self.return_type.as_ref() + } + + pub fn ffi_func(&self) -> &FfiFunction { + &self.ffi_func + } + + pub fn checksum_fn_name(&self) -> &str { + &self.checksum_fn_name + } + + pub fn checksum(&self) -> u16 { + self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self)) + } + + pub fn throws(&self) -> bool { + self.throws.is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + super::throws_name(&self.throws) + } + + pub fn throws_type(&self) -> Option<&Type> { + self.throws.as_ref() + } + + pub fn derive_ffi_func(&mut self) -> Result<()> { + assert!(!self.ffi_func.name.is_empty()); + self.ffi_func.init( + self.return_type.as_ref().map(Into::into), + self.arguments.iter().map(Into::into), + ); + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.arguments + .iter() + .flat_map(Argument::iter_types) + .chain(self.return_type.iter().flat_map(Type::iter_types)), + ) + } +} + +impl From<uniffi_meta::FnParamMetadata> for Argument { + fn from(meta: uniffi_meta::FnParamMetadata) -> Self { + Argument { + name: meta.name, + type_: meta.ty, + by_ref: meta.by_ref, + optional: meta.optional, + default: meta.default, + } + } +} + +impl From<uniffi_meta::FnMetadata> for Function { + fn from(meta: uniffi_meta::FnMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let is_async = meta.is_async; + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + + let ffi_func = FfiFunction { + name: ffi_name, + is_async, + ..FfiFunction::default() + }; + + Self { + name: meta.name, + module_path: meta.module_path, + is_async, + arguments, + return_type, + ffi_func, + throws: meta.throws, + checksum_fn_name, + checksum: meta.checksum, + } + } +} + +/// Represents an argument to a function/constructor/method call. +/// +/// Each argument has a name and a type, along with some optional metadata. +#[derive(Debug, Clone, Checksum)] +pub struct Argument { + pub(super) name: String, + pub(super) type_: Type, + pub(super) by_ref: bool, + pub(super) optional: bool, + pub(super) default: Option<Literal>, +} + +impl Argument { + pub fn name(&self) -> &str { + &self.name + } + + pub fn by_ref(&self) -> bool { + self.by_ref + } + + pub fn is_trait_ref(&self) -> bool { + matches!(&self.type_, Type::Object { imp, .. } if *imp == ObjectImpl::Trait) + } + + pub fn default_value(&self) -> Option<&Literal> { + self.default.as_ref() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.type_.iter_types() + } +} + +impl AsType for Argument { + fn as_type(&self) -> Type { + self.type_.clone() + } +} + +impl From<&Argument> for FfiArgument { + fn from(a: &Argument) -> FfiArgument { + FfiArgument { + name: a.name.clone(), + type_: (&a.type_).into(), + } + } +} + +/// Combines the return and throws type of a function/method +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)] +pub struct ResultType { + pub return_type: Option<Type>, + pub throws_type: Option<Type>, +} + +impl ResultType { + /// Get the `T` parameters for the `FutureCallback<T>` for this ResultType + pub fn future_callback_param(&self) -> FfiType { + match &self.return_type { + Some(t) => t.into(), + None => FfiType::UInt8, + } + } +} + +/// Implemented by function-like types (Function, Method, Constructor) +pub trait Callable { + fn arguments(&self) -> Vec<&Argument>; + fn return_type(&self) -> Option<Type>; + fn throws_type(&self) -> Option<Type>; + fn is_async(&self) -> bool; + fn result_type(&self) -> ResultType { + ResultType { + return_type: self.return_type(), + throws_type: self.throws_type(), + } + } + + // Quick way to get the rust future scaffolding function that corresponds to our return type. + + fn ffi_rust_future_poll(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_poll(self.return_type().map(Into::into)) + .name() + .to_owned() + } + + fn ffi_rust_future_cancel(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_cancel(self.return_type().map(Into::into)) + .name() + .to_owned() + } + + fn ffi_rust_future_complete(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_complete(self.return_type().map(Into::into)) + .name() + .to_owned() + } + + fn ffi_rust_future_free(&self, ci: &ComponentInterface) -> String { + ci.ffi_rust_future_free(self.return_type().map(Into::into)) + .name() + .to_owned() + } +} + +impl Callable for Function { + fn arguments(&self) -> Vec<&Argument> { + self.arguments() + } + + fn return_type(&self) -> Option<Type> { + self.return_type().cloned() + } + + fn throws_type(&self) -> Option<Type> { + self.throws_type().cloned() + } + + fn is_async(&self) -> bool { + self.is_async + } +} + +// Needed because Askama likes to add extra refs to variables +impl<T: Callable> Callable for &T { + fn arguments(&self) -> Vec<&Argument> { + (*self).arguments() + } + + fn return_type(&self) -> Option<Type> { + (*self).return_type() + } + + fn throws_type(&self) -> Option<Type> { + (*self).throws_type() + } + + fn is_async(&self) -> bool { + (*self).is_async() + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + use super::*; + + #[test] + fn test_minimal_and_rich_function() -> Result<()> { + let ci = ComponentInterface::from_webidl( + r#" + namespace test { + void minimal(); + [Throws=TestError] + sequence<string?> rich(u32 arg1, TestDict arg2); + }; + [Error] + enum TestError { "err" }; + dictionary TestDict { + u32 field; + }; + "#, + "crate_name", + )?; + + let func1 = ci.get_function_definition("minimal").unwrap(); + assert_eq!(func1.name(), "minimal"); + assert!(func1.return_type().is_none()); + assert!(func1.throws_type().is_none()); + assert_eq!(func1.arguments().len(), 0); + + let func2 = ci.get_function_definition("rich").unwrap(); + assert_eq!(func2.name(), "rich"); + assert_eq!( + func2.return_type().unwrap(), + &Type::Sequence { + inner_type: Box::new(Type::Optional { + inner_type: Box::new(Type::String) + }) + } + ); + assert!( + matches!(func2.throws_type(), Some(Type::Enum { name, .. }) if name == "TestError" && ci.is_name_used_as_error(name)) + ); + assert_eq!(func2.arguments().len(), 2); + assert_eq!(func2.arguments()[0].name(), "arg1"); + assert_eq!(func2.arguments()[0].as_type(), Type::UInt32); + assert_eq!(func2.arguments()[1].name(), "arg2"); + assert!( + matches!(func2.arguments()[1].as_type(), Type::Record { name, .. } if name == "TestDict") + ); + Ok(()) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/mod.rs new file mode 100644 index 0000000000..8e4df2149b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs @@ -0,0 +1,1234 @@ +/* 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/. */ + +//! # Component Interface Definition. +//! +//! This module provides an abstract representation of the interface provided by a UniFFI Rust Component, +//! in high-level terms suitable for translation into target consumer languages such as Kotlin +//! and Swift. It also provides facilities for parsing a WebIDL interface definition file into such a +//! representation. +//! +//! The entrypoint to this crate is the `ComponentInterface` struct, which holds a complete definition +//! of the interface provided by a component, in two parts: +//! +//! * The high-level consumer API, in terms of objects and records and methods and so-on +//! * The low-level FFI contract through which the foreign language code can call into Rust. +//! +//! That's really the key concept of this crate so it's worth repeating: a `ComponentInterface` completely +//! defines the shape and semantics of an interface between the Rust-based implementation of a component +//! and its foreign language consumers, including details like: +//! +//! * The names of all symbols in the compiled object file +//! * The type and arity of all exported functions +//! * The layout and conventions used for all arguments and return types +//! +//! If you have a dynamic library compiled from a Rust Component using this crate, and a foreign +//! language binding generated from the same `ComponentInterface` using the same version of this +//! module, then there should be no opportunities for them to disagree on how the two sides should +//! interact. +//! +//! General and incomplete TODO list for this thing: +//! +//! * It should prevent user error and the possibility of generating bad code by doing (at least) +//! the following checks: +//! * No duplicate names (types, methods, args, etc) +//! * No shadowing of builtin names, or names we use in code generation +//! We expect that if the user actually does one of these things, then they *should* get a compile +//! error when trying to build the component, because the codegen will be invalid. But we can't +//! guarantee that there's not some edge-case where it produces valid-but-incorrect code. +//! +//! * There is a *lot* of cloning going on, in the spirit of "first make it work". There's probably +//! a good opportunity here for e.g. interned strings, but we're nowhere near the point were we need +//! that kind of optimization just yet. +//! +//! * Error messages and general developer experience leave a lot to be desired. + +use std::{ + collections::{btree_map::Entry, BTreeMap, BTreeSet, HashSet}, + iter, +}; + +use anyhow::{anyhow, bail, ensure, Result}; + +pub mod universe; +pub use uniffi_meta::{AsType, ExternalKind, ObjectImpl, Type}; +use universe::{TypeIterator, TypeUniverse}; + +mod callbacks; +pub use callbacks::CallbackInterface; +mod enum_; +pub use enum_::{Enum, Variant}; +mod function; +pub use function::{Argument, Callable, Function, ResultType}; +mod object; +pub use object::{Constructor, Method, Object, UniffiTrait}; +mod record; +pub use record::{Field, Record}; + +pub mod ffi; +pub use ffi::{FfiArgument, FfiFunction, FfiType}; +pub use uniffi_meta::Radix; +use uniffi_meta::{ + ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata, + UniffiTraitMetadata, UNIFFI_CONTRACT_VERSION, +}; +pub type Literal = LiteralMetadata; + +/// The main public interface for this module, representing the complete details of an interface exposed +/// by a rust component and the details of consuming it via an extern-C FFI layer. +#[derive(Debug, Default)] +pub struct ComponentInterface { + /// All of the types used in the interface. + // We can't checksum `self.types`, but its contents are implied by the other fields + // anyway, so it's safe to ignore it. + pub(super) types: TypeUniverse, + /// The high-level API provided by the component. + enums: BTreeMap<String, Enum>, + records: BTreeMap<String, Record>, + functions: Vec<Function>, + objects: Vec<Object>, + callback_interfaces: Vec<CallbackInterface>, + // Type names which were seen used as an error. + errors: HashSet<String>, + // Types which were seen used as callback interface error. + callback_interface_throws_types: BTreeSet<Type>, +} + +impl ComponentInterface { + pub fn new(crate_name: &str) -> Self { + assert!(!crate_name.is_empty()); + Self { + types: TypeUniverse::new(NamespaceMetadata { + crate_name: crate_name.to_string(), + ..Default::default() + }), + ..Default::default() + } + } + + /// Parse a `ComponentInterface` from a string containing a WebIDL definition. + pub fn from_webidl(idl: &str, module_path: &str) -> Result<Self> { + ensure!( + !module_path.is_empty(), + "you must specify a valid crate name" + ); + let group = uniffi_udl::parse_udl(idl, module_path)?; + Self::from_metadata(group) + } + + /// Create a `ComponentInterface` from a `MetadataGroup` + /// Public so that external binding generators can use it. + pub fn from_metadata(group: uniffi_meta::MetadataGroup) -> Result<Self> { + let mut ci = Self { + types: TypeUniverse::new(group.namespace.clone()), + ..Default::default() + }; + ci.add_metadata(group)?; + Ok(ci) + } + + /// Add a metadata group to a `ComponentInterface`. + pub fn add_metadata(&mut self, group: uniffi_meta::MetadataGroup) -> Result<()> { + if self.types.namespace.name.is_empty() { + self.types.namespace = group.namespace.clone(); + } else if self.types.namespace != group.namespace { + bail!( + "Namespace mismatch: {:?} - {:?}", + group.namespace, + self.types.namespace + ); + } + // Unconditionally add the String type, which is used by the panic handling + self.types.add_known_type(&uniffi_meta::Type::String)?; + crate::macro_metadata::add_group_to_ci(self, group)?; + Ok(()) + } + + /// The string namespace within which this API should be presented to the caller. + /// + /// This string would typically be used to prefix function names in the FFI, to build + /// a package or module name for the foreign language, etc. + pub fn namespace(&self) -> &str { + &self.types.namespace.name + } + + pub fn uniffi_contract_version(&self) -> u32 { + // This is set by the scripts in the version-mismatch fixture + let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION"); + match force_version { + Ok(v) if !v.is_empty() => v.parse().unwrap(), + _ => UNIFFI_CONTRACT_VERSION, + } + } + + /// Get the definitions for every Enum type in the interface. + pub fn enum_definitions(&self) -> impl Iterator<Item = &Enum> { + self.enums.values() + } + + /// Get an Enum definition by name, or None if no such Enum is defined. + pub fn get_enum_definition(&self, name: &str) -> Option<&Enum> { + self.enums.get(name) + } + + /// Get the definitions for every Record type in the interface. + pub fn record_definitions(&self) -> impl Iterator<Item = &Record> { + self.records.values() + } + + /// Get a Record definition by name, or None if no such Record is defined. + pub fn get_record_definition(&self, name: &str) -> Option<&Record> { + self.records.get(name) + } + + /// Get the definitions for every Function in the interface. + pub fn function_definitions(&self) -> &[Function] { + &self.functions + } + + /// Get a Function definition by name, or None if no such Function is defined. + pub fn get_function_definition(&self, name: &str) -> Option<&Function> { + // TODO: probably we could store these internally in a HashMap to make this easier? + self.functions.iter().find(|f| f.name == name) + } + + /// Get the definitions for every Object type in the interface. + pub fn object_definitions(&self) -> &[Object] { + &self.objects + } + + /// Get an Object definition by name, or None if no such Object is defined. + pub fn get_object_definition(&self, name: &str) -> Option<&Object> { + // TODO: probably we could store these internally in a HashMap to make this easier? + self.objects.iter().find(|o| o.name == name) + } + + /// Get the definitions for every Callback Interface type in the interface. + pub fn callback_interface_definitions(&self) -> &[CallbackInterface] { + &self.callback_interfaces + } + + /// Get a Callback interface definition by name, or None if no such interface is defined. + pub fn get_callback_interface_definition(&self, name: &str) -> Option<&CallbackInterface> { + // TODO: probably we could store these internally in a HashMap to make this easier? + self.callback_interfaces.iter().find(|o| o.name == name) + } + + /// Get the definitions for every Method type in the interface. + pub fn iter_callables(&self) -> impl Iterator<Item = &dyn Callable> { + // Each of the `as &dyn Callable` casts is a trivial cast, but it seems like the clearest + // way to express the logic in the current Rust + #[allow(trivial_casts)] + self.function_definitions() + .iter() + .map(|f| f as &dyn Callable) + .chain(self.objects.iter().flat_map(|o| { + o.constructors() + .into_iter() + .map(|c| c as &dyn Callable) + .chain(o.methods().into_iter().map(|m| m as &dyn Callable)) + })) + } + + /// Should we generate read (and lift) functions for errors? + /// + /// This is a workaround for the fact that lower/write can't be generated for some errors, + /// specifically errors that are defined as flat in the UDL, but actually have fields in the + /// Rust source. + pub fn should_generate_error_read(&self, e: &Enum) -> bool { + // We can and should always generate read() methods for fielded errors + let fielded = !e.is_flat(); + // For flat errors, we should only generate read() methods if we need them to support + // callback interface errors + let used_in_callback_interface = self + .callback_interface_definitions() + .iter() + .flat_map(|cb| cb.methods()) + .any(|m| m.throws_type() == Some(&e.as_type())); + + self.is_name_used_as_error(&e.name) && (fielded || used_in_callback_interface) + } + + /// Get details about all `Type::External` types. + /// Returns an iterator of (name, crate_name, kind) + pub fn iter_external_types( + &self, + ) -> impl Iterator<Item = (&String, String, ExternalKind, bool)> { + self.types.iter_known_types().filter_map(|t| match t { + Type::External { + name, + module_path, + kind, + tagged, + .. + } => Some(( + name, + module_path.split("::").next().unwrap().to_string(), + *kind, + *tagged, + )), + _ => None, + }) + } + + /// Get details about all `Type::Custom` types + pub fn iter_custom_types(&self) -> impl Iterator<Item = (&String, &Type)> { + self.types.iter_known_types().filter_map(|t| match t { + Type::Custom { name, builtin, .. } => Some((name, &**builtin)), + _ => None, + }) + } + + /// Iterate over all known types in the interface. + pub fn iter_types(&self) -> impl Iterator<Item = &Type> { + self.types.iter_known_types() + } + + /// Get a specific type + pub fn get_type(&self, name: &str) -> Option<Type> { + self.types.get_type_definition(name) + } + + /// Iterate over all types contained in the given item. + /// + /// This method uses `iter_types` to iterate over the types contained within the given type, + /// but additionally recurses into the definition of user-defined types like records and enums + /// to yield the types that *they* contain. + fn iter_types_in_item<'a>(&'a self, item: &'a Type) -> impl Iterator<Item = &'a Type> + 'a { + RecursiveTypeIterator::new(self, item) + } + + /// Check whether the given item contains any (possibly nested) Type::Object references. + /// + /// This is important to know in language bindings that cannot integrate object types + /// tightly with the host GC, and hence need to perform manual destruction of objects. + pub fn item_contains_object_references(&self, item: &Type) -> bool { + self.iter_types_in_item(item) + .any(|t| matches!(t, Type::Object { .. })) + } + + /// Check whether the given item contains any (possibly nested) unsigned types + pub fn item_contains_unsigned_types(&self, item: &Type) -> bool { + self.iter_types_in_item(item) + .any(|t| matches!(t, Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64)) + } + + /// Check whether the interface contains any optional types + pub fn contains_optional_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Optional { .. })) + } + + /// Check whether the interface contains any sequence types + pub fn contains_sequence_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Sequence { .. })) + } + + /// Check whether the interface contains any map types + pub fn contains_map_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Map { .. })) + } + + // The namespace to use in crate-level FFI function definitions. Not used as the ffi + // namespace for types - each type has its own `module_path` which is used for them. + fn ffi_namespace(&self) -> &str { + &self.types.namespace.crate_name + } + + /// Builtin FFI function to get the current contract version + /// This is needed so that the foreign language bindings can check that they are using the same + /// ABI as the scaffolding + pub fn ffi_uniffi_contract_version(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_uniffi_contract_version", self.ffi_namespace()), + is_async: false, + arguments: vec![], + return_type: Some(FfiType::UInt32), + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function for allocating a new `RustBuffer`. + /// This is needed so that the foreign language bindings can create buffers in which to pass + /// complex data types across the FFI. + pub fn ffi_rustbuffer_alloc(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_alloc", self.ffi_namespace()), + is_async: false, + arguments: vec![FfiArgument { + name: "size".to_string(), + type_: FfiType::Int32, + }], + return_type: Some(FfiType::RustBuffer(None)), + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for copying foreign-owned bytes + /// This is needed so that the foreign language bindings can create buffers in which to pass + /// complex data types across the FFI. + pub fn ffi_rustbuffer_from_bytes(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_from_bytes", self.ffi_namespace()), + is_async: false, + arguments: vec![FfiArgument { + name: "bytes".to_string(), + type_: FfiType::ForeignBytes, + }], + return_type: Some(FfiType::RustBuffer(None)), + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for freeing a `RustBuffer`. + /// This is needed so that the foreign language bindings can free buffers in which they received + /// complex data types returned across the FFI. + pub fn ffi_rustbuffer_free(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_free", self.ffi_namespace()), + is_async: false, + arguments: vec![FfiArgument { + name: "buf".to_string(), + type_: FfiType::RustBuffer(None), + }], + return_type: None, + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for reserving extra space in a `RustBuffer`. + /// This is needed so that the foreign language bindings can grow buffers used for passing + /// complex data types across the FFI. + pub fn ffi_rustbuffer_reserve(&self) -> FfiFunction { + FfiFunction { + name: format!("ffi_{}_rustbuffer_reserve", self.ffi_namespace()), + is_async: false, + arguments: vec![ + FfiArgument { + name: "buf".to_string(), + type_: FfiType::RustBuffer(None), + }, + FfiArgument { + name: "additional".to_string(), + type_: FfiType::Int32, + }, + ], + return_type: Some(FfiType::RustBuffer(None)), + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function to set the Rust Future continuation callback + pub fn ffi_rust_future_continuation_callback_set(&self) -> FfiFunction { + FfiFunction { + name: format!( + "ffi_{}_rust_future_continuation_callback_set", + self.ffi_namespace() + ), + arguments: vec![FfiArgument { + name: "callback".to_owned(), + type_: FfiType::RustFutureContinuationCallback, + }], + return_type: None, + is_async: false, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function to poll a Rust future. + pub fn ffi_rust_future_poll(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_poll", return_ffi_type), + is_async: false, + arguments: vec![ + FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }, + // Data to pass to the continuation + FfiArgument { + name: "uniffi_callback".to_owned(), + type_: FfiType::RustFutureContinuationData, + }, + ], + return_type: None, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function to complete a Rust future and get it's result. + /// + /// We generate one of these for each FFI return type. + pub fn ffi_rust_future_complete(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_complete", return_ffi_type.clone()), + is_async: false, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }], + return_type: return_ffi_type, + has_rust_call_status_arg: true, + is_object_free_function: false, + } + } + + /// Builtin FFI function for cancelling a Rust Future + pub fn ffi_rust_future_cancel(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_cancel", return_ffi_type), + is_async: false, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }], + return_type: None, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + /// Builtin FFI function for freeing a Rust Future + pub fn ffi_rust_future_free(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { + FfiFunction { + name: self.rust_future_ffi_fn_name("rust_future_free", return_ffi_type), + is_async: false, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::RustFutureHandle, + }], + return_type: None, + has_rust_call_status_arg: false, + is_object_free_function: false, + } + } + + fn rust_future_ffi_fn_name(&self, base_name: &str, return_ffi_type: Option<FfiType>) -> String { + let namespace = self.ffi_namespace(); + match return_ffi_type { + Some(t) => match t { + FfiType::UInt8 => format!("ffi_{namespace}_{base_name}_u8"), + FfiType::Int8 => format!("ffi_{namespace}_{base_name}_i8"), + FfiType::UInt16 => format!("ffi_{namespace}_{base_name}_u16"), + FfiType::Int16 => format!("ffi_{namespace}_{base_name}_i16"), + FfiType::UInt32 => format!("ffi_{namespace}_{base_name}_u32"), + FfiType::Int32 => format!("ffi_{namespace}_{base_name}_i32"), + FfiType::UInt64 => format!("ffi_{namespace}_{base_name}_u64"), + FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"), + FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"), + FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"), + FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"), + FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"), + _ => unimplemented!("FFI return type: {t:?}"), + }, + None => format!("ffi_{namespace}_{base_name}_void"), + } + } + + /// Does this interface contain async functions? + pub fn has_async_fns(&self) -> bool { + self.iter_ffi_function_definitions().any(|f| f.is_async()) + } + + /// Iterate over `T` parameters of the `FutureCallback<T>` callbacks in this interface + pub fn iter_future_callback_params(&self) -> impl Iterator<Item = FfiType> { + let unique_results = self + .iter_callables() + .map(|c| c.result_type().future_callback_param()) + .collect::<BTreeSet<_>>(); + unique_results.into_iter() + } + + /// Iterate over return/throws types for async functions + pub fn iter_async_result_types(&self) -> impl Iterator<Item = ResultType> { + let unique_results = self + .iter_callables() + .map(|c| c.result_type()) + .collect::<BTreeSet<_>>(); + unique_results.into_iter() + } + + /// List the definitions of all FFI functions in the interface. + /// + /// The set of FFI functions is derived automatically from the set of higher-level types + /// along with the builtin FFI helper functions. + pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ { + self.iter_user_ffi_function_definitions() + .cloned() + .chain(self.iter_rust_buffer_ffi_function_definitions()) + .chain(self.iter_futures_ffi_function_definitons()) + .chain(self.iter_checksum_ffi_functions()) + .chain(self.ffi_foreign_executor_callback_set()) + .chain([self.ffi_uniffi_contract_version()]) + } + + /// Alternate version of iter_ffi_function_definitions for languages that don't support async + pub fn iter_ffi_function_definitions_non_async( + &self, + ) -> impl Iterator<Item = FfiFunction> + '_ { + self.iter_user_ffi_function_definitions() + .cloned() + .chain(self.iter_rust_buffer_ffi_function_definitions()) + .chain(self.iter_checksum_ffi_functions()) + .chain([self.ffi_uniffi_contract_version()]) + } + + /// List all FFI functions definitions for user-defined interfaces + /// + /// This includes FFI functions for: + /// - Top-level functions + /// - Object methods + /// - Callback interfaces + pub fn iter_user_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> + '_ { + iter::empty() + .chain( + self.objects + .iter() + .flat_map(|obj| obj.iter_ffi_function_definitions()), + ) + .chain( + self.callback_interfaces + .iter() + .map(|cb| cb.ffi_init_callback()), + ) + .chain(self.functions.iter().map(|f| &f.ffi_func)) + } + + /// List all FFI functions definitions for RustBuffer functionality. + pub fn iter_rust_buffer_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> { + [ + self.ffi_rustbuffer_alloc(), + self.ffi_rustbuffer_from_bytes(), + self.ffi_rustbuffer_free(), + self.ffi_rustbuffer_reserve(), + ] + .into_iter() + } + + /// List all FFI functions definitions for async functionality. + pub fn iter_futures_ffi_function_definitons(&self) -> impl Iterator<Item = FfiFunction> + '_ { + let all_possible_return_ffi_types = [ + Some(FfiType::UInt8), + Some(FfiType::Int8), + Some(FfiType::UInt16), + Some(FfiType::Int16), + Some(FfiType::UInt32), + Some(FfiType::Int32), + Some(FfiType::UInt64), + Some(FfiType::Int64), + Some(FfiType::Float32), + Some(FfiType::Float64), + // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future + // complete scaffolding function, so we just use a placeholder value here. + Some(FfiType::RustArcPtr("".to_owned())), + Some(FfiType::RustBuffer(None)), + None, + ]; + + iter::once(self.ffi_rust_future_continuation_callback_set()).chain( + all_possible_return_ffi_types + .into_iter() + .flat_map(|return_type| { + [ + self.ffi_rust_future_poll(return_type.clone()), + self.ffi_rust_future_cancel(return_type.clone()), + self.ffi_rust_future_free(return_type.clone()), + self.ffi_rust_future_complete(return_type), + ] + }), + ) + } + + /// The ffi_foreign_executor_callback_set FFI function + /// + /// We only include this in the FFI if the `ForeignExecutor` type is actually used + pub fn ffi_foreign_executor_callback_set(&self) -> Option<FfiFunction> { + if self.types.contains(&Type::ForeignExecutor) { + Some(FfiFunction { + name: format!("ffi_{}_foreign_executor_callback_set", self.ffi_namespace()), + arguments: vec![FfiArgument { + name: "callback".into(), + type_: FfiType::ForeignExecutorCallback, + }], + return_type: None, + is_async: false, + has_rust_call_status_arg: false, + is_object_free_function: false, + }) + } else { + None + } + } + + /// List all API checksums to check + /// + /// Returns a list of (export_symbol_name, checksum) items + pub fn iter_checksums(&self) -> impl Iterator<Item = (String, u16)> + '_ { + let func_checksums = self + .functions + .iter() + .map(|f| (f.checksum_fn_name(), f.checksum())); + let method_checksums = self.objects.iter().flat_map(|o| { + o.methods() + .into_iter() + .map(|m| (m.checksum_fn_name(), m.checksum())) + }); + let constructor_checksums = self.objects.iter().flat_map(|o| { + o.constructors() + .into_iter() + .map(|c| (c.checksum_fn_name(), c.checksum())) + }); + let callback_method_checksums = self.callback_interfaces.iter().flat_map(|cbi| { + cbi.methods().into_iter().filter_map(|m| { + if m.checksum_fn_name().is_empty() { + // UDL-based callbacks don't have checksum functions, skip these + None + } else { + Some((m.checksum_fn_name(), m.checksum())) + } + }) + }); + func_checksums + .chain(method_checksums) + .chain(constructor_checksums) + .chain(callback_method_checksums) + .map(|(fn_name, checksum)| (fn_name.to_string(), checksum)) + } + + pub fn iter_checksum_ffi_functions(&self) -> impl Iterator<Item = FfiFunction> + '_ { + self.iter_checksums().map(|(name, _)| FfiFunction { + name, + is_async: false, + arguments: vec![], + return_type: Some(FfiType::UInt16), + has_rust_call_status_arg: false, + is_object_free_function: false, + }) + } + + // Private methods for building a ComponentInterface. + // + /// Called by `APIBuilder` impls to add a newly-parsed enum definition to the `ComponentInterface`. + pub(super) fn add_enum_definition(&mut self, defn: Enum) -> Result<()> { + match self.enums.entry(defn.name().to_owned()) { + Entry::Vacant(v) => { + self.types.add_known_types(defn.iter_types())?; + v.insert(defn); + } + Entry::Occupied(o) => { + let existing_def = o.get(); + if defn != *existing_def { + bail!( + "Mismatching definition for enum `{}`!\n\ + existing definition: {existing_def:#?},\n\ + new definition: {defn:#?}", + defn.name(), + ); + } + } + } + + Ok(()) + } + + /// Adds a newly-parsed record definition to the `ComponentInterface`. + pub(super) fn add_record_definition(&mut self, defn: Record) -> Result<()> { + match self.records.entry(defn.name().to_owned()) { + Entry::Vacant(v) => { + self.types.add_known_types(defn.iter_types())?; + v.insert(defn); + } + Entry::Occupied(o) => { + let existing_def = o.get(); + if defn != *existing_def { + bail!( + "Mismatching definition for record `{}`!\n\ + existing definition: {existing_def:#?},\n\ + new definition: {defn:#?}", + defn.name(), + ); + } + } + } + + Ok(()) + } + + /// Called by `APIBuilder` impls to add a newly-parsed function definition to the `ComponentInterface`. + pub(super) fn add_function_definition(&mut self, defn: Function) -> Result<()> { + // Since functions are not a first-class type, we have to check for duplicates here + // rather than relying on the type-finding pass to catch them. + if self.functions.iter().any(|f| f.name == defn.name) { + bail!("duplicate function definition: \"{}\"", defn.name); + } + if self.types.get_type_definition(defn.name()).is_some() { + bail!("Conflicting type definition for \"{}\"", defn.name()); + } + self.types.add_known_types(defn.iter_types())?; + self.functions.push(defn); + + Ok(()) + } + + pub(super) fn add_constructor_meta(&mut self, meta: ConstructorMetadata) -> Result<()> { + let object = get_object(&mut self.objects, &meta.self_name) + .ok_or_else(|| anyhow!("add_constructor_meta: object {} not found", &meta.self_name))?; + let defn: Constructor = meta.into(); + + self.types.add_known_types(defn.iter_types())?; + object.constructors.push(defn); + + Ok(()) + } + + pub(super) fn add_method_meta(&mut self, meta: impl Into<Method>) -> Result<()> { + let mut method: Method = meta.into(); + let object = get_object(&mut self.objects, &method.object_name) + .ok_or_else(|| anyhow!("add_method_meta: object {} not found", &method.object_name))?; + + self.types.add_known_types(method.iter_types())?; + method.object_impl = object.imp; + object.methods.push(method); + Ok(()) + } + + pub(super) fn add_uniffitrait_meta(&mut self, meta: UniffiTraitMetadata) -> Result<()> { + let object = get_object(&mut self.objects, meta.self_name()) + .ok_or_else(|| anyhow!("add_uniffitrait_meta: object not found"))?; + let ut: UniffiTrait = meta.into(); + self.types.add_known_types(ut.iter_types())?; + object.uniffi_traits.push(ut); + Ok(()) + } + + pub(super) fn add_object_meta(&mut self, meta: ObjectMetadata) -> Result<()> { + self.add_object_definition(meta.into()) + } + + /// Called by `APIBuilder` impls to add a newly-parsed object definition to the `ComponentInterface`. + fn add_object_definition(&mut self, defn: Object) -> Result<()> { + self.types.add_known_types(defn.iter_types())?; + self.objects.push(defn); + Ok(()) + } + + pub(super) fn note_name_used_as_error(&mut self, name: &str) { + self.errors.insert(name.to_string()); + } + + pub fn is_name_used_as_error(&self, name: &str) -> bool { + self.errors.contains(name) + } + + /// Called by `APIBuilder` impls to add a newly-parsed callback interface definition to the `ComponentInterface`. + pub(super) fn add_callback_interface_definition(&mut self, defn: CallbackInterface) { + self.callback_interfaces.push(defn); + } + + pub(super) fn add_trait_method_meta(&mut self, meta: TraitMethodMetadata) -> Result<()> { + if let Some(cbi) = get_callback_interface(&mut self.callback_interfaces, &meta.trait_name) { + // uniffi_meta should ensure that we process callback interface methods in order, double + // check that here + if cbi.methods.len() != meta.index as usize { + bail!( + "UniFFI internal error: callback interface method index mismatch for {}::{} (expected {}, saw {})", + meta.trait_name, + meta.name, + cbi.methods.len(), + meta.index, + ); + } + let method: Method = meta.into(); + if let Some(error) = method.throws_type() { + self.callback_interface_throws_types.insert(error.clone()); + } + self.types.add_known_types(method.iter_types())?; + cbi.methods.push(method); + } else { + self.add_method_meta(meta)?; + } + Ok(()) + } + + /// Perform global consistency checks on the declared interface. + /// + /// This method checks for consistency problems in the declared interface + /// as a whole, and which can only be detected after we've finished defining + /// the entire interface. + pub fn check_consistency(&self) -> Result<()> { + if self.namespace().is_empty() { + bail!("missing namespace definition"); + } + + // Because functions aren't first class types, we need to check here that + // a function name hasn't already been used as a type name. + for f in self.functions.iter() { + if self.types.get_type_definition(f.name()).is_some() { + bail!("Conflicting type definition for \"{}\"", f.name()); + } + } + + for ty in self.iter_types() { + match ty { + Type::Object { name, .. } => { + ensure!( + self.objects.iter().any(|o| o.name == *name), + "Object `{name}` has no definition" + ); + } + Type::Record { name, .. } => { + ensure!( + self.records.contains_key(name), + "Record `{name}` has no definition", + ); + } + Type::Enum { name, .. } => { + ensure!( + self.enums.contains_key(name), + "Enum `{name}` has no definition", + ); + } + _ => {} + } + } + + Ok(()) + } + + /// Automatically derive the low-level FFI functions from the high-level types in the interface. + /// + /// This should only be called after the high-level types have been completed defined, otherwise + /// the resulting set will be missing some entries. + pub fn derive_ffi_funcs(&mut self) -> Result<()> { + for func in self.functions.iter_mut() { + func.derive_ffi_func()?; + } + for obj in self.objects.iter_mut() { + obj.derive_ffi_funcs()?; + } + for callback in self.callback_interfaces.iter_mut() { + callback.derive_ffi_funcs(); + } + Ok(()) + } +} + +fn get_object<'a>(objects: &'a mut [Object], name: &str) -> Option<&'a mut Object> { + objects.iter_mut().find(|o| o.name == name) +} + +fn get_callback_interface<'a>( + callback_interfaces: &'a mut [CallbackInterface], + name: &str, +) -> Option<&'a mut CallbackInterface> { + callback_interfaces.iter_mut().find(|o| o.name == name) +} + +/// Stateful iterator for yielding all types contained in a given type. +/// +/// This struct is the implementation of [`ComponentInterface::iter_types_in_item`] and should be +/// considered an opaque implementation detail. It's a separate struct because I couldn't +/// figure out a way to implement it using iterators and closures that would make the lifetimes +/// work out correctly. +/// +/// The idea here is that we want to yield all the types from `iter_types` on a given type, and +/// additionally we want to recurse into the definition of any user-provided types like records, +/// enums, etc so we can also yield the types contained therein. +/// +/// To guard against infinite recursion, we maintain a list of previously-seen user-defined +/// types, ensuring that we recurse into the definition of those types only once. To simplify +/// the implementation, we maintain a queue of pending user-defined types that we have seen +/// but not yet recursed into. (Ironically, the use of an explicit queue means our implementation +/// is not actually recursive...) +struct RecursiveTypeIterator<'a> { + /// The [`ComponentInterface`] from which this iterator was created. + ci: &'a ComponentInterface, + /// The currently-active iterator from which we're yielding. + current: TypeIterator<'a>, + /// A set of names of user-defined types that we have already seen. + seen: HashSet<&'a str>, + /// A queue of user-defined types that we need to recurse into. + pending: Vec<&'a Type>, +} + +impl<'a> RecursiveTypeIterator<'a> { + /// Allocate a new `RecursiveTypeIterator` over the given item. + fn new(ci: &'a ComponentInterface, item: &'a Type) -> RecursiveTypeIterator<'a> { + RecursiveTypeIterator { + ci, + // We begin by iterating over the types from the item itself. + current: item.iter_types(), + seen: Default::default(), + pending: Default::default(), + } + } + + /// Add a new type to the queue of pending types, if not previously seen. + fn add_pending_type(&mut self, type_: &'a Type) { + match type_ { + Type::Record { name, .. } + | Type::Enum { name, .. } + | Type::Object { name, .. } + | Type::CallbackInterface { name, .. } => { + if !self.seen.contains(name.as_str()) { + self.pending.push(type_); + self.seen.insert(name.as_str()); + } + } + _ => (), + } + } + + /// Advance the iterator to recurse into the next pending type, if any. + /// + /// This method is called when the current iterator is empty, and it will select + /// the next pending type from the queue and start iterating over its contained types. + /// The return value will be the first item from the new iterator. + fn advance_to_next_type(&mut self) -> Option<&'a Type> { + if let Some(next_type) = self.pending.pop() { + // This is a little awkward because the various definition lookup methods return an `Option<T>`. + // In the unlikely event that one of them returns `None` then, rather than trying to advance + // to a non-existent type, we just leave the existing iterator in place and allow the recursive + // call to `next()` to try again with the next pending type. + let next_iter = match next_type { + Type::Record { name, .. } => { + self.ci.get_record_definition(name).map(Record::iter_types) + } + Type::Enum { name, .. } => self.ci.get_enum_definition(name).map(Enum::iter_types), + Type::Object { name, .. } => { + self.ci.get_object_definition(name).map(Object::iter_types) + } + Type::CallbackInterface { name, .. } => self + .ci + .get_callback_interface_definition(name) + .map(CallbackInterface::iter_types), + _ => None, + }; + if let Some(next_iter) = next_iter { + self.current = next_iter; + } + // Advance the new iterator to its first item. If the new iterator happens to be empty, + // this will recurse back in to `advance_to_next_type` until we find one that isn't. + self.next() + } else { + // We've completely finished the iteration over all pending types. + None + } + } +} + +impl<'a> Iterator for RecursiveTypeIterator<'a> { + type Item = &'a Type; + fn next(&mut self) -> Option<Self::Item> { + if let Some(type_) = self.current.next() { + self.add_pending_type(type_); + Some(type_) + } else { + self.advance_to_next_type() + } + } +} + +// Helpers for functions/methods/constructors which all have the same "throws" semantics. +fn throws_name(throws: &Option<Type>) -> Option<&str> { + // Type has no `name()` method, just `canonical_name()` which isn't what we want. + match throws { + None => None, + Some(Type::Enum { name, .. }) => Some(name), + _ => panic!("unknown throw type: {throws:?}"), + } +} + +#[cfg(test)] +mod test { + use super::*; + + // Note that much of the functionality of `ComponentInterface` is tested via its interactions + // with specific member types, in the sub-modules defining those member types. + + #[test] + fn test_duplicate_type_names_are_an_error() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + }; + dictionary Testing { + u32 field; + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "Conflicting type definition for `Testing`! \ + existing definition: Object { module_path: \"crate_name\", name: \"Testing\", imp: Struct }, \ + new definition: Record { module_path: \"crate_name\", name: \"Testing\" }" + ); + + const UDL2: &str = r#" + namespace test{}; + enum Testing { + "one", "two" + }; + [Error] + enum Testing { "three", "four" }; + "#; + let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "Mismatching definition for enum `Testing`!\nexisting definition: Enum { + name: \"Testing\", + module_path: \"crate_name\", + variants: [ + Variant { + name: \"one\", + fields: [], + }, + Variant { + name: \"two\", + fields: [], + }, + ], + flat: true, +}, +new definition: Enum { + name: \"Testing\", + module_path: \"crate_name\", + variants: [ + Variant { + name: \"three\", + fields: [], + }, + Variant { + name: \"four\", + fields: [], + }, + ], + flat: true, +}", + ); + + const UDL3: &str = r#" + namespace test{ + u32 Testing(); + }; + enum Testing { + "one", "two" + }; + "#; + let err = ComponentInterface::from_webidl(UDL3, "crate_name").unwrap_err(); + assert!(format!("{err:#}").contains("Conflicting type definition for \"Testing\"")); + } + + #[test] + fn test_contains_optional_types() { + let mut ci = ComponentInterface { + ..Default::default() + }; + + // check that `contains_optional_types` returns false when there is no Optional type in the interface + assert!(!ci.contains_optional_types()); + + // check that `contains_optional_types` returns true when there is an Optional type in the interface + assert!(ci + .types + .add_known_type(&Type::Optional { + inner_type: Box::new(Type::String) + }) + .is_ok()); + assert!(ci.contains_optional_types()); + } + + #[test] + fn test_contains_sequence_types() { + let mut ci = ComponentInterface { + ..Default::default() + }; + + // check that `contains_sequence_types` returns false when there is no Sequence type in the interface + assert!(!ci.contains_sequence_types()); + + // check that `contains_sequence_types` returns true when there is a Sequence type in the interface + assert!(ci + .types + .add_known_type(&Type::Sequence { + inner_type: Box::new(Type::UInt64) + }) + .is_ok()); + assert!(ci.contains_sequence_types()); + assert!(ci.types.contains(&Type::UInt64)); + } + + #[test] + fn test_contains_map_types() { + let mut ci = ComponentInterface { + ..Default::default() + }; + + // check that `contains_map_types` returns false when there is no Map type in the interface + assert!(!ci.contains_map_types()); + + // check that `contains_map_types` returns true when there is a Map type in the interface + assert!(ci + .types + .add_known_type(&Type::Map { + key_type: Box::new(Type::String), + value_type: Box::new(Type::Boolean) + }) + .is_ok()); + assert!(ci.contains_map_types()); + assert!(ci.types.contains(&Type::String)); + assert!(ci.types.contains(&Type::Boolean)); + } + + #[test] + fn test_no_infinite_recursion_when_walking_types() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + void tester(Testing foo); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert!(!ci.item_contains_unsigned_types(&Type::Object { + name: "Testing".into(), + module_path: "".into(), + imp: ObjectImpl::Struct, + })); + } + + #[test] + fn test_correct_recursion_when_walking_types() { + const UDL: &str = r#" + namespace test{}; + interface TestObj { + void tester(TestRecord foo); + }; + dictionary TestRecord { + NestedRecord bar; + }; + dictionary NestedRecord { + u64 baz; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert!(ci.item_contains_unsigned_types(&Type::Object { + name: "TestObj".into(), + module_path: "".into(), + imp: ObjectImpl::Struct, + })); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/object.rs b/third_party/rust/uniffi_bindgen/src/interface/object.rs new file mode 100644 index 0000000000..942032b3c6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs @@ -0,0 +1,773 @@ +/* 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/. */ + +//! # Object definitions for a `ComponentInterface`. +//! +//! This module converts "interface" definitions from UDL into [`Object`] structures +//! that can be added to a `ComponentInterface`, which are the main way we define stateful +//! objects with behaviour for a UniFFI Rust Component. An [`Object`] is an opaque handle +//! to some state on which methods can be invoked. +//! +//! (The terminology mismatch between "interface" and "object" is a historical artifact of +//! this tool prior to committing to WebIDL syntax). +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! interface Example { +//! constructor(string? name); +//! string my_name(); +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in an [`Object`] member with one [`Constructor`] and one [`Method`] being added +//! to the resulting [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # interface Example { +//! # constructor(string? name); +//! # string my_name(); +//! # }; +//! # "##, "crate_name")?; +//! let obj = ci.get_object_definition("Example").unwrap(); +//! assert_eq!(obj.name(), "Example"); +//! assert_eq!(obj.constructors().len(), 1); +//! assert_eq!(obj.constructors()[0].arguments()[0].name(), "name"); +//! assert_eq!(obj.methods().len(),1 ); +//! assert_eq!(obj.methods()[0].name(), "my_name"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! It's not necessary for all interfaces to have constructors. +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # interface Example {}; +//! # "##, "crate_name")?; +//! let obj = ci.get_object_definition("Example").unwrap(); +//! assert_eq!(obj.name(), "Example"); +//! assert_eq!(obj.constructors().len(), 0); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use std::iter; + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::function::{Argument, Callable}; +use super::{AsType, ObjectImpl, Type, TypeIterator}; + +/// An "object" is an opaque type that is passed around by reference, can +/// have methods called on it, and so on - basically your classic Object Oriented Programming +/// type of deal, except without elaborate inheritance hierarchies. Some can be instantiated. +/// +/// In UDL these correspond to the `interface` keyword. +/// +/// At the FFI layer, objects are represented by an opaque integer handle and a set of functions +/// a common prefix. The object's constructors are functions that return new objects by handle, +/// and its methods are functions that take a handle as first argument. The foreign language +/// binding code is expected to stitch these functions back together into an appropriate class +/// definition (or that language's equivalent thereof). +/// +/// TODO: +/// - maybe "Class" would be a better name than "Object" here? +#[derive(Debug, Clone, Checksum)] +pub struct Object { + pub(super) name: String, + /// How this object is implemented in Rust + pub(super) imp: ObjectImpl, + pub(super) module_path: String, + pub(super) constructors: Vec<Constructor>, + pub(super) methods: Vec<Method>, + // The "trait" methods - they have a (presumably "well known") name, and + // a regular method (albeit with a generated name) + // XXX - this should really be a HashSet, but not enough transient types support hash to make it worthwhile now. + pub(super) uniffi_traits: Vec<UniffiTrait>, + // We don't include the FfiFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func_free: FfiFunction, +} + +impl Object { + pub fn name(&self) -> &str { + &self.name + } + + /// Returns the fully qualified name that should be used by Rust code for this object. + /// Includes `r#`, traits get a leading `dyn`. If we ever supported associated types, then + /// this would also include them. + pub fn rust_name(&self) -> String { + self.imp.rust_name_for(&self.name) + } + + pub fn imp(&self) -> &ObjectImpl { + &self.imp + } + + pub fn constructors(&self) -> Vec<&Constructor> { + self.constructors.iter().collect() + } + + pub fn primary_constructor(&self) -> Option<&Constructor> { + self.constructors + .iter() + .find(|cons| cons.is_primary_constructor()) + } + + pub fn alternate_constructors(&self) -> Vec<&Constructor> { + self.constructors + .iter() + .filter(|cons| !cons.is_primary_constructor()) + .collect() + } + + pub fn methods(&self) -> Vec<&Method> { + self.methods.iter().collect() + } + + pub fn get_method(&self, name: &str) -> Method { + let matches: Vec<_> = self.methods.iter().filter(|m| m.name() == name).collect(); + match matches.len() { + 1 => matches[0].clone(), + n => panic!("{n} methods named {name}"), + } + } + + pub fn uniffi_traits(&self) -> Vec<&UniffiTrait> { + self.uniffi_traits.iter().collect() + } + + pub fn ffi_object_free(&self) -> &FfiFunction { + &self.ffi_func_free + } + + pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> { + iter::once(&self.ffi_func_free) + .chain(self.constructors.iter().map(|f| &f.ffi_func)) + .chain(self.methods.iter().map(|f| &f.ffi_func)) + .chain( + self.uniffi_traits + .iter() + .flat_map(|ut| match ut { + UniffiTrait::Display { fmt: m } + | UniffiTrait::Debug { fmt: m } + | UniffiTrait::Hash { hash: m } => vec![m], + UniffiTrait::Eq { eq, ne } => vec![eq, ne], + }) + .map(|m| &m.ffi_func), + ) + } + + pub fn derive_ffi_funcs(&mut self) -> Result<()> { + assert!(!self.ffi_func_free.name().is_empty()); + self.ffi_func_free.arguments = vec![FfiArgument { + name: "ptr".to_string(), + type_: FfiType::RustArcPtr(self.name.to_string()), + }]; + self.ffi_func_free.return_type = None; + self.ffi_func_free.is_object_free_function = true; + + for cons in self.constructors.iter_mut() { + cons.derive_ffi_func(); + } + for meth in self.methods.iter_mut() { + meth.derive_ffi_func()?; + } + for ut in self.uniffi_traits.iter_mut() { + ut.derive_ffi_func()?; + } + + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.methods + .iter() + .map(Method::iter_types) + .chain(self.uniffi_traits.iter().map(UniffiTrait::iter_types)) + .chain(self.constructors.iter().map(Constructor::iter_types)) + .flatten(), + ) + } +} + +impl AsType for Object { + fn as_type(&self) -> Type { + Type::Object { + name: self.name.clone(), + module_path: self.module_path.clone(), + imp: self.imp, + } + } +} + +impl From<uniffi_meta::ObjectMetadata> for Object { + fn from(meta: uniffi_meta::ObjectMetadata) -> Self { + let ffi_free_name = meta.free_ffi_symbol_name(); + Object { + module_path: meta.module_path, + name: meta.name, + imp: meta.imp, + constructors: Default::default(), + methods: Default::default(), + uniffi_traits: Default::default(), + ffi_func_free: FfiFunction { + name: ffi_free_name, + ..Default::default() + }, + } + } +} + +impl From<uniffi_meta::UniffiTraitMetadata> for UniffiTrait { + fn from(meta: uniffi_meta::UniffiTraitMetadata) -> Self { + match meta { + uniffi_meta::UniffiTraitMetadata::Debug { fmt } => { + UniffiTrait::Debug { fmt: fmt.into() } + } + uniffi_meta::UniffiTraitMetadata::Display { fmt } => { + UniffiTrait::Display { fmt: fmt.into() } + } + uniffi_meta::UniffiTraitMetadata::Eq { eq, ne } => UniffiTrait::Eq { + eq: eq.into(), + ne: ne.into(), + }, + uniffi_meta::UniffiTraitMetadata::Hash { hash } => { + UniffiTrait::Hash { hash: hash.into() } + } + } + } +} + +// Represents a constructor for an object type. +// +// In the FFI, this will be a function that returns a pointer to an instance +// of the corresponding object type. +#[derive(Debug, Clone, Checksum)] +pub struct Constructor { + pub(super) name: String, + pub(super) object_name: String, + pub(super) object_module_path: String, + pub(super) arguments: Vec<Argument>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func: FfiFunction, + pub(super) throws: Option<Type>, + pub(super) checksum_fn_name: String, + // Force a checksum value, or we'll fallback to the trait. + #[checksum_ignore] + pub(super) checksum: Option<u16>, +} + +impl Constructor { + pub fn name(&self) -> &str { + &self.name + } + + pub fn arguments(&self) -> Vec<&Argument> { + self.arguments.iter().collect() + } + + pub fn full_arguments(&self) -> Vec<Argument> { + self.arguments.to_vec() + } + + pub fn ffi_func(&self) -> &FfiFunction { + &self.ffi_func + } + + pub fn checksum_fn_name(&self) -> &str { + &self.checksum_fn_name + } + + pub fn checksum(&self) -> u16 { + self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self)) + } + + pub fn throws(&self) -> bool { + self.throws.is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + super::throws_name(&self.throws) + } + + pub fn throws_type(&self) -> Option<&Type> { + self.throws.as_ref() + } + + pub fn is_primary_constructor(&self) -> bool { + self.name == "new" + } + + fn derive_ffi_func(&mut self) { + assert!(!self.ffi_func.name().is_empty()); + self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); + self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone())); + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.arguments.iter().flat_map(Argument::iter_types)) + } +} + +impl From<uniffi_meta::ConstructorMetadata> for Constructor { + fn from(meta: uniffi_meta::ConstructorMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + + let ffi_func = FfiFunction { + name: ffi_name, + ..FfiFunction::default() + }; + Self { + name: meta.name, + object_name: meta.self_name, + object_module_path: meta.module_path, + arguments, + ffi_func, + throws: meta.throws.map(Into::into), + checksum_fn_name, + checksum: meta.checksum, + } + } +} + +// Represents an instance method for an object type. +// +// The FFI will represent this as a function whose first/self argument is a +// `FfiType::RustArcPtr` to the instance. +#[derive(Debug, Clone, Checksum)] +pub struct Method { + pub(super) name: String, + pub(super) object_name: String, + pub(super) object_module_path: String, + pub(super) is_async: bool, + pub(super) object_impl: ObjectImpl, + pub(super) arguments: Vec<Argument>, + pub(super) return_type: Option<Type>, + // We don't include the FFIFunc in the hash calculation, because: + // - it is entirely determined by the other fields, + // so excluding it is safe. + // - its `name` property includes a checksum derived from the very + // hash value we're trying to calculate here, so excluding it + // avoids a weird circular dependency in the calculation. + #[checksum_ignore] + pub(super) ffi_func: FfiFunction, + pub(super) throws: Option<Type>, + pub(super) takes_self_by_arc: bool, + pub(super) checksum_fn_name: String, + // Force a checksum value, or we'll fallback to the trait. + #[checksum_ignore] + pub(super) checksum: Option<u16>, +} + +impl Method { + pub fn name(&self) -> &str { + &self.name + } + + pub fn is_async(&self) -> bool { + self.is_async + } + + pub fn arguments(&self) -> Vec<&Argument> { + self.arguments.iter().collect() + } + + // Methods have a special implicit first argument for the object instance, + // hence `arguments` and `full_arguments` are different. + pub fn full_arguments(&self) -> Vec<Argument> { + vec![Argument { + name: "ptr".to_string(), + // TODO: ideally we'd get this via `ci.resolve_type_expression` so that it + // is contained in the proper `TypeUniverse`, but this works for now. + type_: Type::Object { + name: self.object_name.clone(), + module_path: self.object_module_path.clone(), + imp: self.object_impl, + }, + by_ref: !self.takes_self_by_arc, + optional: false, + default: None, + }] + .into_iter() + .chain(self.arguments.iter().cloned()) + .collect() + } + + pub fn return_type(&self) -> Option<&Type> { + self.return_type.as_ref() + } + + pub fn ffi_func(&self) -> &FfiFunction { + &self.ffi_func + } + + pub fn checksum_fn_name(&self) -> &str { + &self.checksum_fn_name + } + + pub fn checksum(&self) -> u16 { + self.checksum.unwrap_or_else(|| uniffi_meta::checksum(self)) + } + + pub fn throws(&self) -> bool { + self.throws.is_some() + } + + pub fn throws_name(&self) -> Option<&str> { + super::throws_name(&self.throws) + } + + pub fn throws_type(&self) -> Option<&Type> { + self.throws.as_ref() + } + + pub fn takes_self_by_arc(&self) -> bool { + self.takes_self_by_arc + } + + pub fn derive_ffi_func(&mut self) -> Result<()> { + assert!(!self.ffi_func.name().is_empty()); + self.ffi_func.init( + self.return_type.as_ref().map(Into::into), + self.full_arguments().iter().map(Into::into), + ); + Ok(()) + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + self.arguments + .iter() + .flat_map(Argument::iter_types) + .chain(self.return_type.iter().flat_map(Type::iter_types)), + ) + } +} + +impl From<uniffi_meta::MethodMetadata> for Method { + fn from(meta: uniffi_meta::MethodMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let is_async = meta.is_async; + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + + let ffi_func = FfiFunction { + name: ffi_name, + is_async, + ..FfiFunction::default() + }; + + Self { + name: meta.name, + object_name: meta.self_name, + object_module_path: meta.module_path, + is_async, + object_impl: ObjectImpl::Struct, // will be filled in later + arguments, + return_type, + ffi_func, + throws: meta.throws.map(Into::into), + takes_self_by_arc: meta.takes_self_by_arc, + checksum_fn_name, + checksum: meta.checksum, + } + } +} + +impl From<uniffi_meta::TraitMethodMetadata> for Method { + fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self { + let ffi_name = meta.ffi_symbol_name(); + let checksum_fn_name = meta.checksum_symbol_name(); + let return_type = meta.return_type.map(Into::into); + let arguments = meta.inputs.into_iter().map(Into::into).collect(); + let ffi_func = FfiFunction { + name: ffi_name, + ..FfiFunction::default() + }; + Self { + name: meta.name, + object_name: meta.trait_name, + object_module_path: meta.module_path, + is_async: false, + arguments, + return_type, + throws: meta.throws.map(Into::into), + takes_self_by_arc: meta.takes_self_by_arc, + checksum_fn_name, + checksum: meta.checksum, + ffi_func, + object_impl: ObjectImpl::Struct, + } + } +} + +/// The list of traits we support generating helper methods for. +#[derive(Clone, Debug, Checksum)] +pub enum UniffiTrait { + Debug { fmt: Method }, + Display { fmt: Method }, + Eq { eq: Method, ne: Method }, + Hash { hash: Method }, +} + +impl UniffiTrait { + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new( + match self { + UniffiTrait::Display { fmt: m } + | UniffiTrait::Debug { fmt: m } + | UniffiTrait::Hash { hash: m } => vec![m.iter_types()], + UniffiTrait::Eq { eq, ne } => vec![eq.iter_types(), ne.iter_types()], + } + .into_iter() + .flatten(), + ) + } + + pub fn derive_ffi_func(&mut self) -> Result<()> { + match self { + UniffiTrait::Display { fmt: m } + | UniffiTrait::Debug { fmt: m } + | UniffiTrait::Hash { hash: m } => { + m.derive_ffi_func()?; + } + UniffiTrait::Eq { eq, ne } => { + eq.derive_ffi_func()?; + ne.derive_ffi_func()?; + } + } + Ok(()) + } +} + +impl Callable for Constructor { + fn arguments(&self) -> Vec<&Argument> { + self.arguments() + } + + fn return_type(&self) -> Option<Type> { + Some(Type::Object { + name: self.object_name.clone(), + module_path: self.object_module_path.clone(), + imp: ObjectImpl::Struct, + }) + } + + fn throws_type(&self) -> Option<Type> { + self.throws_type().cloned() + } + + fn is_async(&self) -> bool { + false + } +} + +impl Callable for Method { + fn arguments(&self) -> Vec<&Argument> { + self.arguments() + } + + fn return_type(&self) -> Option<Type> { + self.return_type().cloned() + } + + fn throws_type(&self) -> Option<Type> { + self.throws_type().cloned() + } + + fn is_async(&self) -> bool { + self.is_async + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + use super::*; + + #[test] + fn test_that_all_argument_and_return_types_become_known() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(string? name, u16 age); + sequence<u32> code_points_of_name(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.object_definitions().len(), 1); + ci.get_object_definition("Testing").unwrap(); + + assert_eq!(ci.iter_types().count(), 6); + assert!(ci.iter_types().any(|t| t == &Type::UInt16)); + assert!(ci.iter_types().any(|t| t == &Type::UInt32)); + assert!(ci.iter_types().any(|t| t + == &Type::Sequence { + inner_type: Box::new(Type::UInt32) + })); + assert!(ci.iter_types().any(|t| t == &Type::String)); + assert!(ci.iter_types().any(|t| t + == &Type::Optional { + inner_type: Box::new(Type::String) + })); + assert!(ci + .iter_types() + .any(|t| matches!(t, Type::Object { name, ..} if name == "Testing"))); + } + + #[test] + fn test_alternate_constructors() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + [Name=new_with_u32] + constructor(u32 v); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.object_definitions().len(), 1); + + let obj = ci.get_object_definition("Testing").unwrap(); + assert!(obj.primary_constructor().is_some()); + assert_eq!(obj.alternate_constructors().len(), 1); + assert_eq!(obj.methods().len(), 0); + + let cons = obj.primary_constructor().unwrap(); + assert_eq!(cons.name(), "new"); + assert_eq!(cons.arguments.len(), 0); + assert_eq!(cons.ffi_func.arguments.len(), 0); + + let cons = obj.alternate_constructors()[0]; + assert_eq!(cons.name(), "new_with_u32"); + assert_eq!(cons.arguments.len(), 1); + assert_eq!(cons.ffi_func.arguments.len(), 1); + } + + #[test] + fn test_the_name_new_identifies_the_primary_constructor() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + [Name=newish] + constructor(); + [Name=new] + constructor(u32 v); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.object_definitions().len(), 1); + + let obj = ci.get_object_definition("Testing").unwrap(); + assert!(obj.primary_constructor().is_some()); + assert_eq!(obj.alternate_constructors().len(), 1); + assert_eq!(obj.methods().len(), 0); + + let cons = obj.primary_constructor().unwrap(); + assert_eq!(cons.name(), "new"); + assert_eq!(cons.arguments.len(), 1); + + let cons = obj.alternate_constructors()[0]; + assert_eq!(cons.name(), "newish"); + assert_eq!(cons.arguments.len(), 0); + assert_eq!(cons.ffi_func.arguments.len(), 0); + } + + #[test] + fn test_the_name_new_is_reserved_for_constructors() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + void new(u32 v); + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "the method name \"new\" is reserved for the default constructor" + ); + } + + #[test] + fn test_duplicate_primary_constructors_not_allowed() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + constructor(); + constructor(u32 v); + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!(err.to_string(), "Duplicate interface member name: \"new\""); + + const UDL2: &str = r#" + namespace test{}; + interface Testing { + constructor(); + [Name=new] + constructor(u32 v); + }; + "#; + let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); + assert_eq!(err.to_string(), "Duplicate interface member name: \"new\""); + } + + #[test] + fn test_trait_attribute() { + const UDL: &str = r#" + namespace test{}; + interface NotATrait { + }; + [Trait] + interface ATrait { + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + let obj = ci.get_object_definition("NotATrait").unwrap(); + assert_eq!(obj.imp.rust_name_for(&obj.name), "r#NotATrait"); + let obj = ci.get_object_definition("ATrait").unwrap(); + assert_eq!(obj.imp.rust_name_for(&obj.name), "dyn r#ATrait"); + } + + #[test] + fn test_trait_constructors_not_allowed() { + const UDL: &str = r#" + namespace test{}; + [Trait] + interface Testing { + constructor(); + }; + "#; + let err = ComponentInterface::from_webidl(UDL, "crate_name").unwrap_err(); + assert_eq!( + err.to_string(), + "Trait interfaces can not have constructors: \"new\"" + ); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/record.rs b/third_party/rust/uniffi_bindgen/src/interface/record.rs new file mode 100644 index 0000000000..17d3774a49 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs @@ -0,0 +1,230 @@ +/* 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/. */ + +//! # Record definitions for a `ComponentInterface`. +//! +//! This module converts "dictionary" definitions from UDL into [`Record`] structures +//! that can be added to a `ComponentInterface`, which are the main way we define structured +//! data types for a UniFFI Rust Component. A [`Record`] has a fixed set of named fields, +//! each of a specific type. +//! +//! (The terminology mismatch between "dictionary" and "record" is a historical artifact +//! due to this tool being loosely inspired by WebAssembly Interface Types, which used +//! the term "record" for this sort of data). +//! +//! A declaration in the UDL like this: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! dictionary Example { +//! string name; +//! u32 value; +//! }; +//! # "##, "crate_name")?; +//! # Ok::<(), anyhow::Error>(()) +//! ``` +//! +//! Will result in a [`Record`] member with two [`Field`]s being added to the resulting +//! [`crate::ComponentInterface`]: +//! +//! ``` +//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" +//! # namespace example {}; +//! # dictionary Example { +//! # string name; +//! # u32 value; +//! # }; +//! # "##, "crate_name")?; +//! let record = ci.get_record_definition("Example").unwrap(); +//! assert_eq!(record.name(), "Example"); +//! assert_eq!(record.fields()[0].name(), "name"); +//! assert_eq!(record.fields()[1].name(), "value"); +//! # Ok::<(), anyhow::Error>(()) +//! ``` + +use anyhow::Result; +use uniffi_meta::Checksum; + +use super::Literal; +use super::{AsType, Type, TypeIterator}; + +/// Represents a "data class" style object, for passing around complex values. +/// +/// In the FFI these are represented as a byte buffer, which one side explicitly +/// serializes the data into and the other serializes it out of. So I guess they're +/// kind of like "pass by clone" values. +#[derive(Debug, Clone, PartialEq, Eq, Checksum)] +pub struct Record { + pub(super) name: String, + pub(super) module_path: String, + pub(super) fields: Vec<Field>, +} + +impl Record { + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + Box::new(self.fields.iter().flat_map(Field::iter_types)) + } +} + +impl AsType for Record { + fn as_type(&self) -> Type { + Type::Record { + name: self.name.clone(), + module_path: self.module_path.clone(), + } + } +} + +impl TryFrom<uniffi_meta::RecordMetadata> for Record { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::RecordMetadata) -> Result<Self> { + Ok(Self { + name: meta.name, + module_path: meta.module_path, + fields: meta + .fields + .into_iter() + .map(TryInto::try_into) + .collect::<Result<_>>()?, + }) + } +} + +// Represents an individual field on a Record. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Checksum)] +pub struct Field { + pub(super) name: String, + pub(super) type_: Type, + pub(super) default: Option<Literal>, +} + +impl Field { + pub fn name(&self) -> &str { + &self.name + } + + pub fn default_value(&self) -> Option<&Literal> { + self.default.as_ref() + } + + pub fn iter_types(&self) -> TypeIterator<'_> { + self.type_.iter_types() + } +} + +impl AsType for Field { + fn as_type(&self) -> Type { + self.type_.clone() + } +} + +impl TryFrom<uniffi_meta::FieldMetadata> for Field { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::FieldMetadata) -> Result<Self> { + let name = meta.name; + let type_ = meta.ty; + let default = meta.default; + Ok(Self { + name, + type_, + default, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::ComponentInterface; + use super::*; + use uniffi_meta::Radix; + + #[test] + fn test_multiple_record_types() { + const UDL: &str = r#" + namespace test{}; + dictionary Empty {}; + dictionary Simple { + u32 field; + }; + dictionary Complex { + string? key; + u32 value = 0; + required boolean spin; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.record_definitions().count(), 3); + + let record = ci.get_record_definition("Empty").unwrap(); + assert_eq!(record.name(), "Empty"); + assert_eq!(record.fields().len(), 0); + + let record = ci.get_record_definition("Simple").unwrap(); + assert_eq!(record.name(), "Simple"); + assert_eq!(record.fields().len(), 1); + assert_eq!(record.fields()[0].name(), "field"); + assert_eq!(record.fields()[0].as_type(), Type::UInt32); + assert!(record.fields()[0].default_value().is_none()); + + let record = ci.get_record_definition("Complex").unwrap(); + assert_eq!(record.name(), "Complex"); + assert_eq!(record.fields().len(), 3); + assert_eq!(record.fields()[0].name(), "key"); + assert_eq!( + record.fields()[0].as_type(), + Type::Optional { + inner_type: Box::new(Type::String) + }, + ); + assert!(record.fields()[0].default_value().is_none()); + assert_eq!(record.fields()[1].name(), "value"); + assert_eq!(record.fields()[1].as_type(), Type::UInt32); + assert!(matches!( + record.fields()[1].default_value(), + Some(Literal::UInt(0, Radix::Decimal, Type::UInt32)) + )); + assert_eq!(record.fields()[2].name(), "spin"); + assert_eq!(record.fields()[2].as_type(), Type::Boolean); + assert!(record.fields()[2].default_value().is_none()); + } + + #[test] + fn test_that_all_field_types_become_known() { + const UDL: &str = r#" + namespace test{}; + dictionary Testing { + string? maybe_name; + u32 value; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.record_definitions().count(), 1); + let record = ci.get_record_definition("Testing").unwrap(); + assert_eq!(record.fields().len(), 2); + assert_eq!(record.fields()[0].name(), "maybe_name"); + assert_eq!(record.fields()[1].name(), "value"); + + assert_eq!(ci.iter_types().count(), 4); + assert!(ci.iter_types().any(|t| t == &Type::UInt32)); + assert!(ci.iter_types().any(|t| t == &Type::String)); + assert!(ci.iter_types().any(|t| t + == &Type::Optional { + inner_type: Box::new(Type::String) + })); + assert!(ci + .iter_types() + .any(|t| matches!(t, Type::Record { name, .. } if name == "Testing"))); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/interface/universe.rs b/third_party/rust/uniffi_bindgen/src/interface/universe.rs new file mode 100644 index 0000000000..e69d86e44f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/interface/universe.rs @@ -0,0 +1,136 @@ +/* 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/. */ + +//! The set of all [`Type`]s used in a component interface is represented by a `TypeUniverse`, +//! which can be used by the bindings generator code to determine what type-related helper +//! functions to emit for a given component. +//! +use anyhow::Result; +use std::{collections::hash_map::Entry, collections::BTreeSet, collections::HashMap}; + +pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type, TypeIterator}; + +/// The set of all possible types used in a particular component interface. +/// +/// Every component API uses a finite number of types, including primitive types, API-defined +/// types like records and enums, and recursive types such as sequences of the above. Our +/// component API doesn't support fancy generics so this is a finitely-enumerable set, which +/// is useful to be able to operate on explicitly. +/// +/// You could imagine this struct doing some clever interning of names and so-on in future, +/// to reduce the overhead of passing around [Type] instances. For now we just do a whole +/// lot of cloning. +#[derive(Debug, Default)] +pub(crate) struct TypeUniverse { + /// The unique prefixes that we'll use for namespacing when exposing this component's API. + pub namespace: NamespaceMetadata, + + // Named type definitions (including aliases). + type_definitions: HashMap<String, Type>, + // All the types in the universe, by canonical type name, in a well-defined order. + all_known_types: BTreeSet<Type>, +} + +impl TypeUniverse { + pub fn new(namespace: NamespaceMetadata) -> Self { + Self { + namespace, + ..Default::default() + } + } + + /// Add the definition of a named [Type]. + fn add_type_definition(&mut self, name: &str, type_: &Type) -> Result<()> { + match self.type_definitions.entry(name.to_string()) { + Entry::Occupied(o) => { + // all conflicts have been resolved by now in udl. + // I doubt procmacros could cause this? + assert_eq!(type_, o.get()); + Ok(()) + } + Entry::Vacant(e) => { + e.insert(type_.clone()); + Ok(()) + } + } + } + + /// Get the [Type] corresponding to a given name, if any. + pub(super) fn get_type_definition(&self, name: &str) -> Option<Type> { + self.type_definitions.get(name).cloned() + } + + /// Add a [Type] to the set of all types seen in the component interface. + pub fn add_known_type(&mut self, type_: &Type) -> Result<()> { + // Types are more likely to already be known than not, so avoid unnecessary cloning. + if !self.all_known_types.contains(type_) { + self.all_known_types.insert(type_.to_owned()); + } + match type_ { + Type::UInt8 => self.add_type_definition("u8", type_)?, + Type::Int8 => self.add_type_definition("i8", type_)?, + Type::UInt16 => self.add_type_definition("u16", type_)?, + Type::Int16 => self.add_type_definition("i16", type_)?, + Type::UInt32 => self.add_type_definition("i32", type_)?, + Type::Int32 => self.add_type_definition("u32", type_)?, + Type::UInt64 => self.add_type_definition("u64", type_)?, + Type::Int64 => self.add_type_definition("i64", type_)?, + Type::Float32 => self.add_type_definition("f32", type_)?, + Type::Float64 => self.add_type_definition("f64", type_)?, + Type::Boolean => self.add_type_definition("bool", type_)?, + Type::String => self.add_type_definition("string", type_)?, + Type::Bytes => self.add_type_definition("bytes", type_)?, + Type::Timestamp => self.add_type_definition("timestamp", type_)?, + Type::Duration => self.add_type_definition("duration", type_)?, + Type::ForeignExecutor => { + self.add_type_definition("ForeignExecutor", type_)?; + } + Type::Object { name, .. } + | Type::Record { name, .. } + | Type::Enum { name, .. } + | Type::CallbackInterface { name, .. } + | Type::External { name, .. } => self.add_type_definition(name, type_)?, + Type::Custom { name, builtin, .. } => { + self.add_type_definition(name, type_)?; + self.add_known_type(builtin)?; + } + // Structurally recursive types. + Type::Optional { inner_type, .. } | Type::Sequence { inner_type, .. } => { + self.add_known_type(inner_type)?; + } + Type::Map { + key_type, + value_type, + } => { + self.add_known_type(key_type)?; + self.add_known_type(value_type)?; + } + } + Ok(()) + } + + /// Add many [`Type`]s... + pub fn add_known_types(&mut self, types: TypeIterator<'_>) -> Result<()> { + for t in types { + self.add_known_type(t)? + } + Ok(()) + } + + /// Check if a [Type] is present + pub fn contains(&self, type_: &Type) -> bool { + self.all_known_types.contains(type_) + } + + /// Iterator over all the known types in this universe. + pub fn iter_known_types(&self) -> impl Iterator<Item = &Type> { + self.all_known_types.iter() + } +} + +#[cfg(test)] +mod test_type_universe { + // All the useful functionality of the `TypeUniverse` struct + // is tested as part of the `TypeFinder` and `TypeResolver` test suites. +} diff --git a/third_party/rust/uniffi_bindgen/src/lib.rs b/third_party/rust/uniffi_bindgen/src/lib.rs new file mode 100644 index 0000000000..019b24022f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/lib.rs @@ -0,0 +1,519 @@ +/* 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/. */ + +//! # Uniffi: easily build cross-platform software components in Rust +//! +//! This is a highly-experimental crate for building cross-language software components +//! in Rust, based on things we've learned and patterns we've developed in the +//! [mozilla/application-services](https://github.com/mozilla/application-services) project. +//! +//! The idea is to let you write your code once, in Rust, and then re-use it from many +//! other programming languages via Rust's C-compatible FFI layer and some automagically +//! generated binding code. If you think of it as a kind of [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) +//! wannabe, with a clunkier developer experience but support for more target languages, +//! you'll be pretty close to the mark. +//! +//! Currently supported target languages include Kotlin, Swift and Python. +//! +//! ## Usage +// +//! To build a cross-language component using `uniffi`, follow these steps. +//! +//! ### 1) Specify your Component Interface +//! +//! Start by thinking about the interface you want to expose for use +//! from other languages. Use the Interface Definition Language to specify your interface +//! in a `.udl` file, where it can be processed by the tools from this crate. +//! For example you might define an interface like this: +//! +//! ```text +//! namespace example { +//! u32 foo(u32 bar); +//! } +//! +//! dictionary MyData { +//! u32 num_foos; +//! bool has_a_bar; +//! } +//! ``` +//! +//! ### 2) Implement the Component Interface as a Rust crate +//! +//! With the interface, defined, provide a corresponding implementation of that interface +//! as a standard-looking Rust crate, using functions and structs and so-on. For example +//! an implementation of the above Component Interface might look like this: +//! +//! ```text +//! fn foo(bar: u32) -> u32 { +//! // TODO: a better example! +//! bar + 42 +//! } +//! +//! struct MyData { +//! num_foos: u32, +//! has_a_bar: bool +//! } +//! ``` +//! +//! ### 3) Generate and include component scaffolding from the UDL file +//! +//! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`. +//! Then add to your crate `uniffi_build` under `[build-dependencies]`. +//! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding` +//! to process your `.udl` file. This will generate some Rust code to be included in the top-level source +//! code of your crate. If your UDL file is named `example.udl`, then your build script would call: +//! +//! ```text +//! uniffi_build::generate_scaffolding("src/example.udl") +//! ``` +//! +//! This would output a rust file named `example.uniffi.rs`, ready to be +//! included into the code of your rust crate like this: +//! +//! ```text +//! include_scaffolding!("example"); +//! ``` +//! +//! ### 4) Generate foreign language bindings for the library +//! +//! The `uniffi-bindgen` utility provides a command-line tool that can produce code to +//! consume the Rust library in any of several supported languages. +//! It is done by calling (in kotlin for example): +//! +//! ```text +//! uniffi-bindgen --language kotlin ./src/example.udl +//! ``` +//! +//! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings +//! to load and use the compiled rust code via its C-compatible FFI. +//! + +#![warn(rust_2018_idioms, unused_qualifications)] +#![allow(unknown_lints)] + +use anyhow::{anyhow, bail, Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; +use fs_err::{self as fs, File}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::io::prelude::*; +use std::io::ErrorKind; +use std::{collections::HashMap, process::Command}; + +pub mod backend; +pub mod bindings; +pub mod interface; +pub mod library_mode; +pub mod macro_metadata; +pub mod scaffolding; + +use bindings::TargetLanguage; +pub use interface::ComponentInterface; +use scaffolding::RustScaffolding; + +/// Trait for bindings configuration. Each bindings language defines one of these. +/// +/// BindingsConfigs are initially loaded from `uniffi.toml` file. Then the trait methods are used +/// to fill in missing values. +pub trait BindingsConfig: DeserializeOwned { + /// Update missing values using the `ComponentInterface` + fn update_from_ci(&mut self, ci: &ComponentInterface); + + /// Update missing values using the dylib file for the main crate, when in library mode. + /// + /// cdylib_name will be the library filename without the leading `lib` and trailing extension + fn update_from_cdylib_name(&mut self, cdylib_name: &str); + + /// Update missing values from config instances from dependent crates + /// + /// config_map maps crate names to config instances. This is mostly used to set up external + /// types. + fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>); +} + +/// Binding generator config with no members +#[derive(Clone, Debug, Deserialize, Hash, PartialEq, PartialOrd, Ord, Eq)] +pub struct EmptyBindingsConfig; + +impl BindingsConfig for EmptyBindingsConfig { + fn update_from_ci(&mut self, _ci: &ComponentInterface) {} + fn update_from_cdylib_name(&mut self, _cdylib_name: &str) {} + fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {} +} + +/// A trait representing a UniFFI Binding Generator +/// +/// External crates that implement binding generators, should implement this type +/// and call the [`generate_external_bindings`] using a type that implements this trait. +pub trait BindingGenerator: Sized { + /// Handles configuring the bindings + type Config: BindingsConfig; + + /// Writes the bindings to the output directory + /// + /// # Arguments + /// - `ci`: A [`ComponentInterface`] representing the interface + /// - `config`: An instance of the [`BindingsConfig`] associated with this type + /// - `out_dir`: The path to where the binding generator should write the output bindings + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Self::Config, + out_dir: &Utf8Path, + ) -> Result<()>; + + /// Check if `library_path` used by library mode is valid for this generator + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>; +} + +struct BindingGeneratorDefault { + target_languages: Vec<TargetLanguage>, + try_format_code: bool, +} + +impl BindingGenerator for BindingGeneratorDefault { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Self::Config, + out_dir: &Utf8Path, + ) -> Result<()> { + for &language in &self.target_languages { + bindings::write_bindings( + &config.bindings, + ci, + out_dir, + language, + self.try_format_code, + )?; + } + Ok(()) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + for &language in &self.target_languages { + if cdylib_name.is_none() && language != TargetLanguage::Swift { + bail!("Generate bindings for {language} requires a cdylib, but {library_path} was given"); + } + } + Ok(()) + } +} + +/// Generate bindings for an external binding generator +/// Ideally, this should replace the [`generate_bindings`] function below +/// +/// Implements an entry point for external binding generators. +/// The function does the following: +/// - It parses the `udl` in a [`ComponentInterface`] +/// - Parses the `uniffi.toml` and loads it into the type that implements [`BindingsConfig`] +/// - Creates an instance of [`BindingGenerator`], based on type argument `B`, and run [`BindingGenerator::write_bindings`] on it +/// +/// # Arguments +/// - `binding_generator`: Type that implements BindingGenerator +/// - `udl_file`: The path to the UDL file +/// - `config_file_override`: The path to the configuration toml file, most likely called `uniffi.toml`. If [`None`], the function will try to guess based on the crate's root. +/// - `out_dir_override`: The path to write the bindings to. If [`None`], it will be the path to the parent directory of the `udl_file` +/// - `library_file`: The path to a dynamic library to attempt to extract the definitions from and extend the component interface with. No extensions to component interface occur if it's [`None`] +/// - `crate_name`: Override the default crate name that is guessed from UDL file path. +pub fn generate_external_bindings<T: BindingGenerator>( + binding_generator: T, + udl_file: impl AsRef<Utf8Path>, + config_file_override: Option<impl AsRef<Utf8Path>>, + out_dir_override: Option<impl AsRef<Utf8Path>>, + library_file: Option<impl AsRef<Utf8Path>>, + crate_name: Option<&str>, +) -> Result<()> { + let crate_name = crate_name + .map(|c| Ok(c.to_string())) + .unwrap_or_else(|| crate_name_from_cargo_toml(udl_file.as_ref()))?; + let mut component = parse_udl(udl_file.as_ref(), &crate_name)?; + if let Some(ref library_file) = library_file { + macro_metadata::add_to_ci_from_library(&mut component, library_file.as_ref())?; + } + let crate_root = &guess_crate_root(udl_file.as_ref()).context("Failed to guess crate root")?; + + let config_file_override = config_file_override.as_ref().map(|p| p.as_ref()); + + let config = { + let mut config = load_initial_config::<T::Config>(crate_root, config_file_override)?; + config.update_from_ci(&component); + if let Some(ref library_file) = library_file { + if let Some(cdylib_name) = crate::library_mode::calc_cdylib_name(library_file.as_ref()) + { + config.update_from_cdylib_name(cdylib_name) + } + }; + config + }; + + let out_dir = get_out_dir( + udl_file.as_ref(), + out_dir_override.as_ref().map(|p| p.as_ref()), + )?; + binding_generator.write_bindings(&component, &config, &out_dir) +} + +// Generate the infrastructural Rust code for implementing the UDL interface, +// such as the `extern "C"` function definitions and record data types. +// Locates and parses Cargo.toml to determine the name of the crate. +pub fn generate_component_scaffolding( + udl_file: &Utf8Path, + out_dir_override: Option<&Utf8Path>, + format_code: bool, +) -> Result<()> { + let component = parse_udl(udl_file, &crate_name_from_cargo_toml(udl_file)?)?; + generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code) +} + +// Generate the infrastructural Rust code for implementing the UDL interface, +// such as the `extern "C"` function definitions and record data types, using +// the specified crate name. +pub fn generate_component_scaffolding_for_crate( + udl_file: &Utf8Path, + crate_name: &str, + out_dir_override: Option<&Utf8Path>, + format_code: bool, +) -> Result<()> { + let component = parse_udl(udl_file, crate_name)?; + generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code) +} + +fn generate_component_scaffolding_inner( + component: ComponentInterface, + udl_file: &Utf8Path, + out_dir_override: Option<&Utf8Path>, + format_code: bool, +) -> Result<()> { + let file_stem = udl_file.file_stem().context("not a file")?; + let filename = format!("{file_stem}.uniffi.rs"); + let out_path = get_out_dir(udl_file, out_dir_override)?.join(filename); + let mut f = File::create(&out_path)?; + write!(f, "{}", RustScaffolding::new(&component, file_stem)) + .context("Failed to write output file")?; + if format_code { + format_code_with_rustfmt(&out_path)?; + } + Ok(()) +} + +// Generate the bindings in the target languages that call the scaffolding +// Rust code. +pub fn generate_bindings( + udl_file: &Utf8Path, + config_file_override: Option<&Utf8Path>, + target_languages: Vec<TargetLanguage>, + out_dir_override: Option<&Utf8Path>, + library_file: Option<&Utf8Path>, + crate_name: Option<&str>, + try_format_code: bool, +) -> Result<()> { + generate_external_bindings( + BindingGeneratorDefault { + target_languages, + try_format_code, + }, + udl_file, + config_file_override, + out_dir_override, + library_file, + crate_name, + ) +} + +pub fn print_repr(library_path: &Utf8Path) -> Result<()> { + let metadata = macro_metadata::extract_from_library(library_path)?; + println!("{metadata:#?}"); + Ok(()) +} + +// Given the path to a UDL file, locate and parse the corresponding Cargo.toml to determine +// the library crate name. +// Note that this is largely a copy of code in uniffi_macros/src/util.rs, but sharing it +// isn't trivial and it's not particularly complicated so we've just copied it. +fn crate_name_from_cargo_toml(udl_file: &Utf8Path) -> Result<String> { + #[derive(Deserialize)] + struct CargoToml { + package: Package, + #[serde(default)] + lib: Lib, + } + + #[derive(Deserialize)] + struct Package { + name: String, + } + + #[derive(Default, Deserialize)] + struct Lib { + name: Option<String>, + } + + let file = guess_crate_root(udl_file)?.join("Cargo.toml"); + let cargo_toml_bytes = + fs::read(file).context("Can't find Cargo.toml to determine the crate name")?; + + let cargo_toml = toml::from_slice::<CargoToml>(&cargo_toml_bytes)?; + + let lib_crate_name = cargo_toml + .lib + .name + .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_")); + + Ok(lib_crate_name) +} + +/// Guess the root directory of the crate from the path of its UDL file. +/// +/// For now, we assume that the UDL file is in `./src/something.udl` relative +/// to the crate root. We might consider something more sophisticated in +/// future. +pub fn guess_crate_root(udl_file: &Utf8Path) -> Result<&Utf8Path> { + let path_guess = udl_file + .parent() + .context("UDL file has no parent folder!")? + .parent() + .context("UDL file has no grand-parent folder!")?; + if !path_guess.join("Cargo.toml").is_file() { + bail!("UDL file does not appear to be inside a crate") + } + Ok(path_guess) +} + +fn get_out_dir(udl_file: &Utf8Path, out_dir_override: Option<&Utf8Path>) -> Result<Utf8PathBuf> { + Ok(match out_dir_override { + Some(s) => { + // Create the directory if it doesn't exist yet. + fs::create_dir_all(s)?; + s.canonicalize_utf8().context("Unable to find out-dir")? + } + None => udl_file + .parent() + .context("File has no parent directory")? + .to_owned(), + }) +} + +fn parse_udl(udl_file: &Utf8Path, crate_name: &str) -> Result<ComponentInterface> { + let udl = fs::read_to_string(udl_file) + .with_context(|| format!("Failed to read UDL from {udl_file}"))?; + let group = uniffi_udl::parse_udl(&udl, crate_name)?; + ComponentInterface::from_metadata(group) +} + +fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> { + let status = Command::new("rustfmt").arg(path).status().map_err(|e| { + let ctx = match e.kind() { + ErrorKind::NotFound => "formatting was requested, but rustfmt was not found", + _ => "unknown error when calling rustfmt", + }; + anyhow!(e).context(ctx) + })?; + if !status.success() { + bail!("rustmt failed when formatting scaffolding. Note: --no-format can be used to skip formatting"); + } + Ok(()) +} + +fn load_initial_config<Config: DeserializeOwned>( + crate_root: &Utf8Path, + config_file_override: Option<&Utf8Path>, +) -> Result<Config> { + let path = match config_file_override { + Some(cfg) => Some(cfg.to_owned()), + None => crate_root.join("uniffi.toml").canonicalize_utf8().ok(), + }; + let toml_config = match path { + Some(path) => { + let contents = fs::read_to_string(path).context("Failed to read config file")?; + toml::de::from_str(&contents)? + } + None => toml::Value::from(toml::value::Table::default()), + }; + Ok(toml_config.try_into()?) +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + #[serde(default)] + bindings: bindings::Config, +} + +impl BindingsConfig for Config { + fn update_from_ci(&mut self, ci: &ComponentInterface) { + self.bindings.kotlin.update_from_ci(ci); + self.bindings.swift.update_from_ci(ci); + self.bindings.python.update_from_ci(ci); + self.bindings.ruby.update_from_ci(ci); + } + + fn update_from_cdylib_name(&mut self, cdylib_name: &str) { + self.bindings.kotlin.update_from_cdylib_name(cdylib_name); + self.bindings.swift.update_from_cdylib_name(cdylib_name); + self.bindings.python.update_from_cdylib_name(cdylib_name); + self.bindings.ruby.update_from_cdylib_name(cdylib_name); + } + + fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) { + self.bindings.kotlin.update_from_dependency_configs( + config_map + .iter() + .map(|(key, config)| (*key, &config.bindings.kotlin)) + .collect(), + ); + self.bindings.swift.update_from_dependency_configs( + config_map + .iter() + .map(|(key, config)| (*key, &config.bindings.swift)) + .collect(), + ); + self.bindings.python.update_from_dependency_configs( + config_map + .iter() + .map(|(key, config)| (*key, &config.bindings.python)) + .collect(), + ); + self.bindings.ruby.update_from_dependency_configs( + config_map + .iter() + .map(|(key, config)| (*key, &config.bindings.ruby)) + .collect(), + ); + } +} + +// FIXME(HACK): +// Include the askama config file into the build. +// That way cargo tracks the file and other tools relying on file tracking see it as well. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1774585 +// In the future askama should handle that itself by using the `track_path::path` API, +// see https://github.com/rust-lang/rust/pull/84029 +#[allow(dead_code)] +mod __unused { + const _: &[u8] = include_bytes!("../askama.toml"); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_guessing_of_crate_root_directory_from_udl_file() { + // When running this test, this will be the ./uniffi_bindgen directory. + let this_crate_root = Utf8PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + let example_crate_root = this_crate_root + .parent() + .expect("should have a parent directory") + .join("examples/arithmetic"); + assert_eq!( + guess_crate_root(&example_crate_root.join("src/arthmetic.udl")).unwrap(), + example_crate_root + ); + + let not_a_crate_root = &this_crate_root.join("src/templates"); + assert!(guess_crate_root(¬_a_crate_root.join("src/example.udl")).is_err()); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/library_mode.rs b/third_party/rust/uniffi_bindgen/src/library_mode.rs new file mode 100644 index 0000000000..f170ea5e91 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/library_mode.rs @@ -0,0 +1,287 @@ +/* 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/. */ + +/// Alternative implementation for the `generate` command, that we plan to eventually replace the current default with. +/// +/// Traditionally, users would invoke `uniffi-bindgen generate` to generate bindings for a single crate, passing it the UDL file, config file, etc. +/// +/// library_mode is a new way to generate bindings for multiple crates at once. +/// Users pass the path to the build cdylib file and UniFFI figures everything out, leveraging `cargo_metadata`, the metadata UniFFI stores inside exported symbols in the dylib, etc. +/// +/// This brings several advantages.: +/// - No more need to specify the dylib in the `uniffi.toml` file(s) +/// - UniFFI can figure out the dependencies based on the dylib exports and generate the sources for +/// all of them at once. +/// - UniFFI can figure out the package/module names for each crate, eliminating the external +/// package maps. +use crate::{ + bindings::TargetLanguage, load_initial_config, macro_metadata, BindingGenerator, + BindingGeneratorDefault, BindingsConfig, ComponentInterface, Result, +}; +use anyhow::{bail, Context}; +use camino::Utf8Path; +use cargo_metadata::{MetadataCommand, Package}; +use std::{ + collections::{HashMap, HashSet}, + fs, +}; +use uniffi_meta::{ + create_metadata_groups, fixup_external_type, group_metadata, Metadata, MetadataGroup, +}; + +/// Generate foreign bindings +/// +/// Returns the list of sources used to generate the bindings, in no particular order. +pub fn generate_bindings( + library_path: &Utf8Path, + crate_name: Option<String>, + target_languages: &[TargetLanguage], + out_dir: &Utf8Path, + try_format_code: bool, +) -> Result<Vec<Source<crate::Config>>> { + generate_external_bindings( + BindingGeneratorDefault { + target_languages: target_languages.into(), + try_format_code, + }, + library_path, + crate_name, + out_dir, + ) +} + +/// Generate foreign bindings +/// +/// Returns the list of sources used to generate the bindings, in no particular order. +pub fn generate_external_bindings<T: BindingGenerator>( + binding_generator: T, + library_path: &Utf8Path, + crate_name: Option<String>, + out_dir: &Utf8Path, +) -> Result<Vec<Source<T::Config>>> { + let cargo_metadata = MetadataCommand::new() + .exec() + .context("error running cargo metadata")?; + let cdylib_name = calc_cdylib_name(library_path); + binding_generator.check_library_path(library_path, cdylib_name)?; + + let mut sources = find_sources(&cargo_metadata, library_path, cdylib_name)?; + for i in 0..sources.len() { + // Partition up the sources list because we're eventually going to call + // `update_from_dependency_configs()` which requires an exclusive reference to one source and + // shared references to all other sources. + let (sources_before, rest) = sources.split_at_mut(i); + let (source, sources_after) = rest.split_first_mut().unwrap(); + let other_sources = sources_before.iter().chain(sources_after.iter()); + // Calculate which configs come from dependent crates + let dependencies = + HashSet::<&str>::from_iter(source.package.dependencies.iter().map(|d| d.name.as_str())); + let config_map: HashMap<&str, &T::Config> = other_sources + .filter_map(|s| { + dependencies + .contains(s.package.name.as_str()) + .then_some((s.crate_name.as_str(), &s.config)) + }) + .collect(); + // We can finally call update_from_dependency_configs + source.config.update_from_dependency_configs(config_map); + } + fs::create_dir_all(out_dir)?; + if let Some(crate_name) = &crate_name { + let old_elements = sources.drain(..); + let mut matches: Vec<_> = old_elements + .filter(|s| &s.crate_name == crate_name) + .collect(); + match matches.len() { + 0 => bail!("Crate {crate_name} not found in {library_path}"), + 1 => sources.push(matches.pop().unwrap()), + n => bail!("{n} crates named {crate_name} found in {library_path}"), + } + } + + for source in sources.iter() { + binding_generator.write_bindings(&source.ci, &source.config, out_dir)?; + } + + Ok(sources) +} + +// A single source that we generate bindings for +#[derive(Debug)] +pub struct Source<Config: BindingsConfig> { + pub package: Package, + pub crate_name: String, + pub ci: ComponentInterface, + pub config: Config, +} + +// If `library_path` is a C dynamic library, return its name +pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> { + let cdylib_extentions = [".so", ".dll", ".dylib"]; + let filename = library_path.file_name()?; + let filename = filename.strip_prefix("lib").unwrap_or(filename); + for ext in cdylib_extentions { + if let Some(f) = filename.strip_suffix(ext) { + return Some(f); + } + } + None +} + +fn find_sources<Config: BindingsConfig>( + cargo_metadata: &cargo_metadata::Metadata, + library_path: &Utf8Path, + cdylib_name: Option<&str>, +) -> Result<Vec<Source<Config>>> { + let items = macro_metadata::extract_from_library(library_path)?; + let mut metadata_groups = create_metadata_groups(&items); + group_metadata(&mut metadata_groups, items)?; + + // Collect and process all UDL from all groups at the start - the fixups + // of external types makes this tricky to do as we finalize the group. + let mut udl_items: HashMap<String, MetadataGroup> = HashMap::new(); + + for group in metadata_groups.values() { + let package = find_package_by_crate_name(cargo_metadata, &group.namespace.crate_name)?; + let crate_root = package + .manifest_path + .parent() + .context("manifest path has no parent")?; + let crate_name = group.namespace.crate_name.clone(); + if let Some(mut metadata_group) = load_udl_metadata(group, crate_root, &crate_name)? { + // fixup the items. + metadata_group.items = metadata_group + .items + .into_iter() + .map(|item| fixup_external_type(item, &metadata_groups)) + // some items are both in UDL and library metadata. For many that's fine but + // uniffi-traits aren't trivial to compare meaning we end up with dupes. + // We filter out such problematic items here. + .filter(|item| !matches!(item, Metadata::UniffiTrait { .. })) + .collect(); + udl_items.insert(crate_name, metadata_group); + }; + } + + metadata_groups + .into_values() + .map(|group| { + let package = find_package_by_crate_name(cargo_metadata, &group.namespace.crate_name)?; + let crate_root = package + .manifest_path + .parent() + .context("manifest path has no parent")?; + let crate_name = group.namespace.crate_name.clone(); + let mut ci = ComponentInterface::new(&crate_name); + if let Some(metadata) = udl_items.remove(&crate_name) { + ci.add_metadata(metadata)?; + }; + ci.add_metadata(group)?; + let mut config = load_initial_config::<Config>(crate_root, None)?; + if let Some(cdylib_name) = cdylib_name { + config.update_from_cdylib_name(cdylib_name); + } + config.update_from_ci(&ci); + Ok(Source { + config, + crate_name, + ci, + package, + }) + }) + .collect() +} + +fn find_package_by_crate_name( + metadata: &cargo_metadata::Metadata, + crate_name: &str, +) -> Result<Package> { + let matching: Vec<&Package> = metadata + .packages + .iter() + .filter(|p| { + p.targets + .iter() + .any(|t| t.name.replace('-', "_") == crate_name) + }) + .collect(); + match matching.len() { + 1 => Ok(matching[0].clone()), + n => bail!("cargo metadata returned {n} packages for crate name {crate_name}"), + } +} + +fn load_udl_metadata( + group: &MetadataGroup, + crate_root: &Utf8Path, + crate_name: &str, +) -> Result<Option<MetadataGroup>> { + let udl_items = group + .items + .iter() + .filter_map(|i| match i { + uniffi_meta::Metadata::UdlFile(meta) => Some(meta), + _ => None, + }) + .collect::<Vec<_>>(); + match udl_items.len() { + // No UDL files, load directly from the group + 0 => Ok(None), + // Found a UDL file, use it to load the CI, then add the MetadataGroup + 1 => { + if udl_items[0].module_path != crate_name { + bail!( + "UDL is for crate '{}' but this crate name is '{}'", + udl_items[0].module_path, + crate_name + ); + } + let ci_name = &udl_items[0].file_stub; + let ci_path = crate_root.join("src").join(format!("{ci_name}.udl")); + if ci_path.exists() { + let udl = fs::read_to_string(ci_path)?; + let udl_group = uniffi_udl::parse_udl(&udl, crate_name)?; + Ok(Some(udl_group)) + } else { + bail!("{ci_path} not found"); + } + } + n => bail!("{n} UDL files found for {crate_root}"), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn calc_cdylib_name_is_correct() { + assert_eq!( + "uniffi", + calc_cdylib_name("/path/to/libuniffi.so".into()).unwrap() + ); + assert_eq!( + "uniffi", + calc_cdylib_name("/path/to/libuniffi.dylib".into()).unwrap() + ); + assert_eq!( + "uniffi", + calc_cdylib_name("/path/to/uniffi.dll".into()).unwrap() + ); + } + + /// Right now we unconditionally strip the `lib` prefix. + /// + /// Technically Windows DLLs do not start with a `lib` prefix, + /// but a library name could start with a `lib` prefix. + /// On Linux/macOS this would result in a `liblibuniffi.{so,dylib}` file. + #[test] + #[ignore] // Currently fails. + fn calc_cdylib_name_is_correct_on_windows() { + assert_eq!( + "libuniffi", + calc_cdylib_name("/path/to/libuniffi.dll".into()).unwrap() + ); + } +} diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs new file mode 100644 index 0000000000..7ce6c3a70b --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs @@ -0,0 +1,145 @@ +/* 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/. */ + +use crate::interface::{CallbackInterface, ComponentInterface, Enum, Record, Type}; +use anyhow::{bail, Context}; +use uniffi_meta::{ + create_metadata_groups, group_metadata, EnumMetadata, ErrorMetadata, Metadata, MetadataGroup, +}; + +/// Add Metadata items to the ComponentInterface +/// +/// This function exists to support the transition period where the `uniffi::export` macro can only +/// handle some components. This means that crates need to continue using UDL files to define the +/// parts of the components that aren't supported yet. +/// +/// To make things work, we generate a `ComponentInterface` from the UDL file, then combine it with +/// the `Metadata` items that the macro creates. +pub fn add_to_ci( + iface: &mut ComponentInterface, + metadata_items: Vec<Metadata>, +) -> anyhow::Result<()> { + let mut group_map = create_metadata_groups(&metadata_items); + group_metadata(&mut group_map, metadata_items)?; + for group in group_map.into_values() { + if group.items.is_empty() { + continue; + } + if group.namespace.name != iface.namespace() { + let crate_name = group.namespace.crate_name; + bail!("Found metadata items from crate `{crate_name}`. Use the `--library` to generate bindings for multiple crates") + } + add_group_to_ci(iface, group)?; + } + + Ok(()) +} + +/// Add items from a MetadataGroup to a component interface +pub fn add_group_to_ci(iface: &mut ComponentInterface, group: MetadataGroup) -> anyhow::Result<()> { + if group.namespace.name != iface.namespace() { + bail!( + "Namespace mismatch: {} - {}", + group.namespace.name, + iface.namespace() + ); + } + + for item in group.items { + add_item_to_ci(iface, item)? + } + + iface + .derive_ffi_funcs() + .context("Failed to derive FFI functions")?; + iface + .check_consistency() + .context("ComponentInterface consistency error")?; + Ok(()) +} + +fn add_enum_to_ci( + iface: &mut ComponentInterface, + meta: EnumMetadata, + is_flat: bool, +) -> anyhow::Result<()> { + let ty = Type::Enum { + name: meta.name.clone(), + module_path: meta.module_path.clone(), + }; + iface.types.add_known_type(&ty)?; + + let enum_ = Enum::try_from_meta(meta, is_flat)?; + iface.add_enum_definition(enum_)?; + Ok(()) +} + +fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Result<()> { + match item { + Metadata::Namespace(_) => unreachable!(), + Metadata::UdlFile(_) => (), + Metadata::Func(meta) => { + iface.add_function_definition(meta.into())?; + } + Metadata::Constructor(meta) => { + iface.add_constructor_meta(meta)?; + } + Metadata::Method(meta) => { + iface.add_method_meta(meta)?; + } + Metadata::Record(meta) => { + let ty = Type::Record { + name: meta.name.clone(), + module_path: meta.module_path.clone(), + }; + iface.types.add_known_type(&ty)?; + let record: Record = meta.try_into()?; + iface.add_record_definition(record)?; + } + Metadata::Enum(meta) => { + let flat = meta.variants.iter().all(|v| v.fields.is_empty()); + add_enum_to_ci(iface, meta, flat)?; + } + Metadata::Object(meta) => { + iface.types.add_known_type(&Type::Object { + module_path: meta.module_path.clone(), + name: meta.name.clone(), + imp: meta.imp, + })?; + iface.add_object_meta(meta)?; + } + Metadata::UniffiTrait(meta) => { + iface.add_uniffitrait_meta(meta)?; + } + Metadata::CallbackInterface(meta) => { + iface.types.add_known_type(&Type::CallbackInterface { + module_path: meta.module_path.clone(), + name: meta.name.clone(), + })?; + iface.add_callback_interface_definition(CallbackInterface::new( + meta.name, + meta.module_path, + )); + } + Metadata::TraitMethod(meta) => { + iface.add_trait_method_meta(meta)?; + } + Metadata::Error(meta) => { + iface.note_name_used_as_error(meta.name()); + match meta { + ErrorMetadata::Enum { enum_, is_flat } => { + add_enum_to_ci(iface, enum_, is_flat)?; + } + }; + } + Metadata::CustomType(meta) => { + iface.types.add_known_type(&Type::Custom { + module_path: meta.module_path.clone(), + name: meta.name, + builtin: Box::new(meta.builtin), + })?; + } + } + Ok(()) +} diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs new file mode 100644 index 0000000000..25b5ef17ba --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs @@ -0,0 +1,192 @@ +/* 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/. */ + +use anyhow::{bail, Context}; +use camino::Utf8Path; +use fs_err as fs; +use goblin::{ + archive::Archive, + elf::Elf, + mach::{segment::Section, symbols, Mach, MachO, SingleArch}, + pe::PE, + Object, +}; +use std::collections::HashSet; +use uniffi_meta::Metadata; + +/// Extract metadata written by the `uniffi::export` macro from a library file +/// +/// In addition to generating the scaffolding, that macro and also encodes the +/// `uniffi_meta::Metadata` for the components which can be used to generate the bindings side of +/// the interface. +pub fn extract_from_library(path: &Utf8Path) -> anyhow::Result<Vec<Metadata>> { + extract_from_bytes(&fs::read(path)?) +} + +fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + match Object::parse(file_data)? { + Object::Elf(elf) => extract_from_elf(elf, file_data), + Object::PE(pe) => extract_from_pe(pe, file_data), + Object::Mach(mach) => extract_from_mach(mach, file_data), + Object::Archive(archive) => extract_from_archive(archive, file_data), + Object::Unknown(_) => bail!("Unknown library format"), + } +} + +pub fn extract_from_elf(elf: Elf<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + let mut extracted = ExtractedItems::new(); + let iter = elf + .syms + .iter() + .filter_map(|sym| elf.section_headers.get(sym.st_shndx).map(|sh| (sym, sh))); + + for (sym, sh) in iter { + let name = elf + .strtab + .get_at(sym.st_name) + .context("Error getting symbol name")?; + if is_metadata_symbol(name) { + // Offset relative to the start of the section. + let section_offset = sym.st_value - sh.sh_addr; + // Offset relative to the start of the file contents + extracted.extract_item(name, file_data, (sh.sh_offset + section_offset) as usize)?; + } + } + Ok(extracted.into_metadata()) +} + +pub fn extract_from_pe(pe: PE<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + let mut extracted = ExtractedItems::new(); + for export in pe.exports { + if let Some(name) = export.name { + if is_metadata_symbol(name) { + extracted.extract_item( + name, + file_data, + export.offset.context("Error getting symbol offset")?, + )?; + } + } + } + Ok(extracted.into_metadata()) +} + +pub fn extract_from_mach(mach: Mach<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + match mach { + Mach::Binary(macho) => extract_from_macho(macho, file_data), + // Multi-binary library, just extract the first one + Mach::Fat(multi_arch) => match multi_arch.get(0)? { + SingleArch::MachO(macho) => extract_from_macho(macho, file_data), + SingleArch::Archive(archive) => extract_from_archive(archive, file_data), + }, + } +} + +pub fn extract_from_macho(macho: MachO<'_>, file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { + let mut sections: Vec<Section> = Vec::new(); + for sects in macho.segments.sections() { + sections.extend(sects.map(|r| r.expect("section").0)); + } + let mut extracted = ExtractedItems::new(); + sections.sort_by_key(|s| s.addr); + + // Iterate through the symbols. This picks up symbols from the .o files embedded in a Darwin + // archive. + for (name, nlist) in macho.symbols().flatten() { + // Check that the symbol: + // - Is global (exported) + // - Has type=N_SECT (it's regular data as opposed to something like + // "undefined" or "indirect") + // - Has a metadata symbol name + if nlist.is_global() && nlist.get_type() == symbols::N_SECT && is_metadata_symbol(name) { + let section = §ions[nlist.n_sect]; + // `nlist.n_value` is an address, so we can calculating the offset inside the section + // using the difference between that and `section.addr` + let offset = section.offset as usize + nlist.n_value as usize - section.addr as usize; + extracted.extract_item(name, file_data, offset)?; + } + } + + // Iterate through the exports. This picks up symbols from .dylib files. + for export in macho.exports()? { + let name = &export.name; + if is_metadata_symbol(name) { + extracted.extract_item(name, file_data, export.offset as usize)?; + } + } + Ok(extracted.into_metadata()) +} + +pub fn extract_from_archive( + archive: Archive<'_>, + file_data: &[u8], +) -> anyhow::Result<Vec<Metadata>> { + // Store the names of archive members that have metadata symbols in them + let mut members_to_check: HashSet<&str> = HashSet::new(); + for (member_name, _, symbols) in archive.summarize() { + for name in symbols { + if is_metadata_symbol(name) { + members_to_check.insert(member_name); + } + } + } + + let mut items = vec![]; + for member_name in members_to_check { + items.append( + &mut extract_from_bytes( + archive + .extract(member_name, file_data) + .with_context(|| format!("Failed to extract archive member `{member_name}`"))?, + ) + .with_context(|| { + format!("Failed to extract data from archive member `{member_name}`") + })?, + ); + } + Ok(items) +} + +/// Container for extracted metadata items +#[derive(Default)] +struct ExtractedItems { + items: Vec<Metadata>, + /// symbol names for the extracted items, we use this to ensure that we don't extract the same + /// symbol twice + names: HashSet<String>, +} + +impl ExtractedItems { + fn new() -> Self { + Self::default() + } + + fn extract_item(&mut self, name: &str, file_data: &[u8], offset: usize) -> anyhow::Result<()> { + if self.names.contains(name) { + // Already extracted this item + return Ok(()); + } + + // Use the file data starting from offset, without specifying the end position. We don't + // always know the end position, because goblin reports the symbol size as 0 for PE and + // MachO files. + // + // This works fine, because `MetadataReader` knows when the serialized data is terminated + // and will just ignore the trailing data. + let data = &file_data[offset..]; + self.items.push(Metadata::read(data)?); + self.names.insert(name.to_string()); + Ok(()) + } + + fn into_metadata(self) -> Vec<Metadata> { + self.items + } +} + +fn is_metadata_symbol(name: &str) -> bool { + // Skip the "_" char that Darwin prepends, if present + let name = name.strip_prefix('_').unwrap_or(name); + name.starts_with("UNIFFI_META") +} diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs new file mode 100644 index 0000000000..bc5a0a790f --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/mod.rs @@ -0,0 +1,24 @@ +/* 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/. */ + +use super::ComponentInterface; +use anyhow::Context; +use camino::Utf8Path; + +mod ci; +mod extract; + +pub use ci::{add_group_to_ci, add_to_ci}; +pub use extract::extract_from_library; + +pub fn add_to_ci_from_library( + iface: &mut ComponentInterface, + library_path: &Utf8Path, +) -> anyhow::Result<()> { + add_to_ci( + iface, + extract_from_library(library_path).context("Failed to extract proc-macro metadata")?, + ) + .context("Failed to add proc-macro metadata to ComponentInterface") +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs new file mode 100644 index 0000000000..f3759cf6fa --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs @@ -0,0 +1,109 @@ +/* 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/. */ + +use anyhow::Result; +use askama::Template; +use std::borrow::Borrow; + +use super::interface::*; +use heck::{ToShoutySnakeCase, ToSnakeCase}; + +#[derive(Template)] +#[template(syntax = "rs", escape = "none", path = "scaffolding_template.rs")] +pub struct RustScaffolding<'a> { + ci: &'a ComponentInterface, + udl_base_name: &'a str, +} +impl<'a> RustScaffolding<'a> { + pub fn new(ci: &'a ComponentInterface, udl_base_name: &'a str) -> Self { + Self { ci, udl_base_name } + } +} +mod filters { + use super::*; + + pub fn type_rs(type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Int8 => "i8".into(), + Type::UInt8 => "u8".into(), + Type::Int16 => "i16".into(), + Type::UInt16 => "u16".into(), + Type::Int32 => "i32".into(), + Type::UInt32 => "u32".into(), + Type::Int64 => "i64".into(), + Type::UInt64 => "u64".into(), + Type::Float32 => "f32".into(), + Type::Float64 => "f64".into(), + Type::Boolean => "bool".into(), + Type::String => "String".into(), + Type::Bytes => "Vec<u8>".into(), + Type::Timestamp => "std::time::SystemTime".into(), + Type::Duration => "std::time::Duration".into(), + Type::Enum { name, .. } | Type::Record { name, .. } => format!("r#{name}"), + Type::Object { name, imp, .. } => { + format!("std::sync::Arc<{}>", imp.rust_name_for(name)) + } + Type::CallbackInterface { name, .. } => format!("Box<dyn r#{name}>"), + Type::ForeignExecutor => "::uniffi::ForeignExecutor".into(), + Type::Optional { inner_type } => { + format!("std::option::Option<{}>", type_rs(inner_type)?) + } + Type::Sequence { inner_type } => format!("std::vec::Vec<{}>", type_rs(inner_type)?), + Type::Map { + key_type, + value_type, + } => format!( + "std::collections::HashMap<{}, {}>", + type_rs(key_type)?, + type_rs(value_type)? + ), + Type::Custom { name, .. } => format!("r#{name}"), + Type::External { + name, + kind: ExternalKind::Interface, + .. + } => format!("::std::sync::Arc<r#{name}>"), + Type::External { name, .. } => format!("r#{name}"), + }) + } + + // Map a type to Rust code that specifies the FfiConverter implementation. + // + // This outputs something like `<MyStruct as Lift<crate::UniFfiTag>>` + pub fn ffi_trait(type_: &Type, trait_name: &str) -> Result<String, askama::Error> { + Ok(match type_ { + Type::External { + name, + kind: ExternalKind::Interface, + .. + } => { + format!("<::std::sync::Arc<r#{name}> as ::uniffi::{trait_name}<crate::UniFfiTag>>") + } + _ => format!( + "<{} as ::uniffi::{trait_name}<crate::UniFfiTag>>", + type_rs(type_)? + ), + }) + } + + pub fn return_type<T: Callable>(callable: &T) -> Result<String, askama::Error> { + let return_type = match callable.return_type() { + Some(t) => type_rs(&t)?, + None => "()".to_string(), + }; + match callable.throws_type() { + Some(t) => type_rs(&t)?, + None => "()".to_string(), + }; + Ok(match callable.throws_type() { + Some(e) => format!("::std::result::Result<{return_type}, {}>", type_rs(&e)?), + None => return_type, + }) + } + + // Turns a `crate-name` into the `crate_name` the .rs code needs to specify. + pub fn crate_name_rs(nm: &str) -> Result<String, askama::Error> { + Ok(format!("r#{}", nm.to_string().to_snake_case())) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs new file mode 100644 index 0000000000..64c69e4d8e --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs @@ -0,0 +1,82 @@ +{# +// For each Callback Interface definition, we assume that there is a corresponding trait defined in Rust client code. +// If the UDL callback interface and Rust trait's methods don't match, the Rust compiler will complain. +// We generate: +// * an init function to accept that `ForeignCallback` from the foreign language, and stores it. +// * a holder for a `ForeignCallback`, of type `uniffi::ForeignCallbackInternals`. +// * a proxy `struct` which implements the `trait` that the Callback Interface corresponds to. This +// is the object that client code interacts with. +// - for each method, arguments will be packed into a `RustBuffer` and sent over the `ForeignCallback` to be +// unpacked and called. The return value is packed into another `RustBuffer` and sent back to Rust. +// - a `Drop` `impl`, which tells the foreign language to forget about the real callback object. +#} +{% let trait_name = cbi.name() -%} +{% let trait_impl = format!("UniFFICallbackHandler{}", trait_name) %} +{% let foreign_callback_internals = format!("foreign_callback_{}_internals", trait_name)|upper -%} + +// Register a foreign callback for getting across the FFI. +#[doc(hidden)] +static {{ foreign_callback_internals }}: uniffi::ForeignCallbackInternals = uniffi::ForeignCallbackInternals::new(); + +#[doc(hidden)] +#[no_mangle] +pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::ForeignCallback, _: &mut uniffi::RustCallStatus) { + {{ foreign_callback_internals }}.set_callback(callback); + // The call status should be initialized to CALL_SUCCESS, so no need to modify it. +} + +// Make an implementation which will shell out to the foreign language. +#[doc(hidden)] +#[derive(Debug)] +struct {{ trait_impl }} { + handle: u64 +} + +impl {{ trait_impl }} { + fn new(handle: u64) -> Self { + Self { handle } + } +} + +impl Drop for {{ trait_impl }} { + fn drop(&mut self) { + {{ foreign_callback_internals }}.invoke_callback::<(), crate::UniFfiTag>( + self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() + ) + } +} + +uniffi::deps::static_assertions::assert_impl_all!({{ trait_impl }}: Send); + +impl r#{{ trait_name }} for {{ trait_impl }} { + {%- for meth in cbi.methods() %} + + {#- Method declaration #} + fn r#{{ meth.name() -}} + ({% call rs::arg_list_decl_with_prefix("&self", meth) %}) + {%- match (meth.return_type(), meth.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type.borrow()|type_rs }} + {%- when (Some(return_type), Some(err)) %} -> ::std::result::Result<{{ return_type.borrow()|type_rs }}, {{ err|type_rs }}> + {%- when (None, Some(err)) %} -> ::std::result::Result<(), {{ err|type_rs }}> + {% else -%} + {%- endmatch -%} { + {#- Method body #} + + {#- Packing args into a RustBuffer #} + {% if meth.arguments().len() == 0 -%} + let args_buf = Vec::new(); + {% else -%} + let mut args_buf = Vec::new(); + {% endif -%} + {%- for arg in meth.arguments() %} + {{ arg.as_type().borrow()|ffi_trait("Lower") }}::write(r#{{ arg.name() }}, &mut args_buf); + {%- endfor -%} + let args_rbuf = uniffi::RustBuffer::from_vec(args_buf); + + {#- Calling into foreign code. #} + {{ foreign_callback_internals }}.invoke_callback::<{{ meth|return_type }}, crate::UniFfiTag>(self.handle, {{ loop.index }}, args_rbuf) + } + {%- endfor %} +} + +::uniffi::scaffolding_ffi_converter_callback_interface!(r#{{ trait_name }}, {{ trait_impl }}); diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/Checksums.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/Checksums.rs new file mode 100644 index 0000000000..a59009d169 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/Checksums.rs @@ -0,0 +1,7 @@ +{%- for (name, checksum) in ci.iter_checksums() %} +#[no_mangle] +#[doc(hidden)] +pub extern "C" fn r#{{ name }}() -> u16 { + {{ checksum }} +} +{%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs new file mode 100644 index 0000000000..6b9f96f224 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -0,0 +1,19 @@ +{# +// For each enum declared in the UDL, we assume the caller has provided a corresponding +// rust `enum`. We provide the traits for sending it across the FFI, which will fail to +// compile if the provided struct has a different shape to the one declared in the UDL. +// +// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's +// public so other crates can refer to it via an `[External='crate'] typedef` +#} + +#[::uniffi::derive_enum_for_udl] +enum r#{{ e.name() }} { + {%- for variant in e.variants() %} + r#{{ variant.name() }} { + {%- for field in variant.fields() %} + r#{{ field.name() }}: {{ field.as_type().borrow()|type_rs }}, + {%- endfor %} + }, + {%- endfor %} +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs new file mode 100644 index 0000000000..94538ecaa8 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -0,0 +1,26 @@ +{# +// For each error declared in the UDL, we assume the caller has provided a corresponding +// rust `enum`. We provide the traits for sending it across the FFI, which will fail to +// compile if the provided struct has a different shape to the one declared in the UDL. +// +// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's +// public so other crates can refer to it via an `[External='crate'] typedef` +#} + +#[::uniffi::derive_error_for_udl( + {% if e.is_flat() -%} + flat_error, + {% if ci.should_generate_error_read(e) -%} + with_try_read, + {%- endif %} + {%- endif %} +)] +enum r#{{ e.name() }} { + {%- for variant in e.variants() %} + r#{{ variant.name() }} { + {%- for field in variant.fields() %} + r#{{ field.name() }}: {{ field.as_type().borrow()|type_rs }}, + {%- endfor %} + }, + {%- endfor %} +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs new file mode 100644 index 0000000000..ade1578897 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs @@ -0,0 +1,20 @@ +// Support for external types. + +// Types with an external `FfiConverter`... +{% for (name, crate_name, kind, tagged) in ci.iter_external_types() %} +// The FfiConverter for `{{ name }}` is defined in `{{ crate_name }}` +// If it has its existing FfiConverter defined with a UniFFITag, it needs forwarding. +{% if tagged %} +{%- match kind %} +{%- when ExternalKind::DataClass %} +::uniffi::ffi_converter_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); +{%- when ExternalKind::Interface %} +::uniffi::ffi_converter_arc_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); +{%- endmatch %} +{% endif %} +{%- endfor %} + +// We generate support for each Custom Type and the builtin type it uses. +{%- for (name, builtin) in ci.iter_custom_types() %} +::uniffi::custom_type!(r#{{ name }}, {{builtin|type_rs}}); +{%- endfor -%} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs new file mode 100644 index 0000000000..e2445c670d --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -0,0 +1,89 @@ +// For each Object definition, we assume the caller has provided an appropriately-shaped `struct T` +// with an `impl` for each method on the object. We create an `Arc<T>` for "safely" handing out +// references to these structs to foreign language code, and we provide a `pub extern "C"` function +// corresponding to each method. +// +// (Note that "safely" is in "scare quotes" - that's because we use functions on an `Arc` that +// that are inherently unsafe, but the code we generate is safe in practice.) +// +// If the caller's implementation of the struct does not match with the methods or types specified +// in the UDL, then the rust compiler will complain with a (hopefully at least somewhat helpful!) +// error message when processing this generated code. + +{%- match obj.imp() -%} +{%- when ObjectImpl::Trait %} +#[::uniffi::export_for_udl] +pub trait r#{{ obj.name() }} { + {%- for meth in obj.methods() %} + fn {{ meth.name() }}( + {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %}, + {%- for arg in meth.arguments() %} + {{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match (meth.return_type(), meth.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }}; + {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}>; + {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}>; + {%- when (None, None) %}; + {%- endmatch %} + {% endfor %} +} +{% when ObjectImpl::Struct %} +{%- for tm in obj.uniffi_traits() %} +{% match tm %} +{% when UniffiTrait::Debug { fmt }%} +#[uniffi::export(Debug)] +{% when UniffiTrait::Display { fmt }%} +#[uniffi::export(Display)] +{% when UniffiTrait::Hash { hash }%} +#[uniffi::export(Hash)] +{% when UniffiTrait::Eq { eq, ne }%} +#[uniffi::export(Eq)] +{% endmatch %} +{% endfor %} +#[::uniffi::derive_object_for_udl] +struct {{ obj.rust_name() }} { } + +{%- for cons in obj.constructors() %} +#[::uniffi::export_for_udl(constructor)] +impl {{ obj.rust_name() }} { + pub fn r#{{ cons.name() }}( + {%- for arg in cons.arguments() %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match (cons.return_type(), cons.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }} + {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}> + {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}> + {%- when (None, None) %} + {%- endmatch %} + { + unreachable!() + } +} +{%- endfor %} + +{%- for meth in obj.methods() %} +#[::uniffi::export_for_udl] +impl {{ obj.rust_name() }} { + pub fn r#{{ meth.name() }}( + {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %}, + {%- for arg in meth.arguments() %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match (meth.return_type(), meth.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }} + {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}> + {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}> + {%- when (None, None) %} + {%- endmatch %} + { + unreachable!() + } +} +{%- endfor %} + +{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs new file mode 100644 index 0000000000..85e131dd8c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -0,0 +1,16 @@ +{# +// For each record declared in the UDL, we assume the caller has provided a corresponding +// rust `struct` with the declared fields. We provide the traits for sending it across the FFI. +// If the caller's struct does not match the shape and types declared in the UDL then the rust +// compiler will complain with a type error. +// +// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's +// public so other crates can refer to it via an `[External='crate'] typedef` +#} + +#[::uniffi::derive_record_for_udl] +struct r#{{ rec.name() }} { + {%- for field in rec.fields() %} + r#{{ field.name() }}: {{ field.as_type().borrow()|type_rs }}, + {%- endfor %} +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs new file mode 100644 index 0000000000..0707feef27 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ReexportUniFFIScaffolding.rs @@ -0,0 +1,28 @@ +// Code to re-export the UniFFI scaffolding functions. +// +// Rust won't always re-export the functions from dependencies +// ([rust-lang#50007](https://github.com/rust-lang/rust/issues/50007)) +// +// A workaround for this is to have the dependent crate reference a function from its dependency in +// an extern "C" function. This is clearly hacky and brittle, but at least we have some unittests +// that check if this works (fixtures/reexport-scaffolding-macro). +// +// The main way we use this macro is for that contain multiple UniFFI components (libxul, +// megazord). The combined library has a cargo dependency for each component and calls +// uniffi_reexport_scaffolding!() for each one. + +#[allow(missing_docs)] +#[doc(hidden)] +pub const fn uniffi_reexport_hack() {} + +#[doc(hidden)] +#[macro_export] +macro_rules! uniffi_reexport_scaffolding { + () => { + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn {{ ci.namespace() }}_uniffi_reexport_hack() { + $crate::uniffi_reexport_hack() + } + }; +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs new file mode 100644 index 0000000000..eeee0f5ee2 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs @@ -0,0 +1,15 @@ +#[::uniffi::export_for_udl] +pub fn r#{{ func.name() }}( + {%- for arg in func.arguments() %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} +) +{%- match (func.return_type(), func.throws_type()) %} +{%- when (Some(return_type), None) %} -> {{ return_type|type_rs }} +{%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}> +{%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}> +{%- when (None, None) %} +{%- endmatch %} +{ + unreachable!() +} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/UdlMetadata.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/UdlMetadata.rs new file mode 100644 index 0000000000..0b800343d6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/UdlMetadata.rs @@ -0,0 +1,16 @@ + +/// Export info about the UDL while used to create us +/// See `uniffi_bindgen::macro_metadata` for how this is used. + +// ditto for info about the UDL which spawned us. +{%- let const_udl_var = "UNIFFI_META_CONST_UDL_{}"|format(ci.namespace().to_shouty_snake_case()) %} +{%- let static_udl_var = "UNIFFI_META_UDL_{}"|format(ci.namespace().to_shouty_snake_case()) %} + +const {{ const_udl_var }}: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::UDL_FILE) + .concat_str("{{ ci.types.namespace.crate_name }}") + .concat_str("{{ ci.namespace() }}") + .concat_str("{{ udl_base_name }}"); + +#[doc(hidden)] +#[no_mangle] +pub static {{ static_udl_var }}: [u8; {{ const_udl_var }}.size] = {{ const_udl_var }}.into_array(); diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs new file mode 100644 index 0000000000..8b1f94cd1c --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/macros.rs @@ -0,0 +1,28 @@ +{# +// Template to receive calls into rust. + +// Arglist as used in the _UniFFILib function declarations. +// Note unfiltered name but type_ffi filters. +-#} +{%- macro arg_list_ffi_decl(func) %} + {%- for arg in func.arguments() %} + r#{{- arg.name() }}: {{ arg.type_().borrow()|type_ffi -}}, + {%- endfor %} + call_status: &mut uniffi::RustCallStatus +{%- endmacro -%} + +{%- macro arg_list_decl_with_prefix(prefix, meth) %} + {{- prefix -}} + {%- if meth.arguments().len() > 0 %}, {# whitespace #} + {%- for arg in meth.arguments() %} + r#{{- arg.name() }}: {{ arg.as_type().borrow()|type_rs -}}{% if loop.last %}{% else %},{% endif %} + {%- endfor %} + {%- endif %} +{%- endmacro -%} + +{% macro return_signature(func) %} +{%- match func.return_type() %} +{%- when Some with (return_type) %} -> {{ return_type|ffi_trait("LowerReturn") }}::ReturnType +{%- else -%} +{%- endmatch -%} +{%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs new file mode 100644 index 0000000000..c88e204e97 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/scaffolding_template.rs @@ -0,0 +1,52 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +{% import "macros.rs" as rs %} + +::uniffi::setup_scaffolding!("{{ ci.namespace() }}"); + +{% include "UdlMetadata.rs" %} + +{% for ty in ci.iter_types() %} +{%- match ty %} +{%- when Type::Map { key_type: k, value_type: v } -%} +{# Next comment MUST be after the line to be in the compiler output #} +uniffi::deps::static_assertions::assert_impl_all!({{ k|type_rs }}: ::std::cmp::Eq, ::std::hash::Hash); // record<{{ k|type_rs }}, {{ v|type_rs }}> +{%- else %} +{%- endmatch %} +{% endfor %} + +{% for e in ci.enum_definitions() %} +{% if ci.is_name_used_as_error(e.name()) %} +// Error definitions, corresponding to `error` in the UDL. +{% include "ErrorTemplate.rs" %} +{% else %} +// Enum definitions, corresponding to `enum` in UDL. +{% include "EnumTemplate.rs" %} +{% endif %} +{% endfor %} + +// Record definitions, implemented as method-less structs, corresponding to `dictionary` objects. +{% for rec in ci.record_definitions() %} +{% include "RecordTemplate.rs" %} +{% endfor %} + +// Top level functions, corresponding to UDL `namespace` functions. +{%- for func in ci.function_definitions() %} +{% include "TopLevelFunctionTemplate.rs" %} +{% endfor -%} + +// Object definitions, corresponding to UDL `interface` definitions. +{% for obj in ci.object_definitions() %} +{% include "ObjectTemplate.rs" %} +{% endfor %} + +// Callback Interface definitions, corresponding to UDL `callback interface` definitions. +{% for cbi in ci.callback_interface_definitions() %} +{% include "CallbackInterfaceTemplate.rs" %} +{% endfor %} + +// External and Wrapped types +{% include "ExternalTypesTemplate.rs" %} + +// Export scaffolding checksums for UDL items +{% include "Checksums.rs" %} |