Import 'diplomat_core' crate
Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
Bug: 391960687
Test: m libdiplomat_core
Change-Id: I7798f0b48bca10e2c0eadbdb28e643a36d1a4f9d
diff --git a/crates/diplomat_core/.android-checksum.json b/crates/diplomat_core/.android-checksum.json
new file mode 100644
index 0000000..824c778
--- /dev/null
+++ b/crates/diplomat_core/.android-checksum.json
@@ -0,0 +1 @@
+{"package":null,"files":{".cargo-checksum.json":"dfcb117cf89415f5fe4b58ae0032a0da4af01dfa95853a0809eb7a9e6283f83d","Android.bp":"4dc55c04acc59075c2aef2bac12effaa4d0d6081946be54a048bb2a0fd90f877","Cargo.toml":"c38dc98680fc373efbb68d73dad5a32770fbf44afd4d8a253169f3592c05f345","LICENSE":"c96314154285c10cace9fbdd749bb99df065bc09c718ca11eb3c9bcc3fedc823","LICENSE-APACHE":"c96314154285c10cace9fbdd749bb99df065bc09c718ca11eb3c9bcc3fedc823","LICENSE-MIT":"82963c45175dab0ab1d5a10c32b27b6ece4089ff17deca7f922923c53a04935a","METADATA":"ca8df89910cabf708bdc4f3b0284b46cfbccf466cb6c305f10b30167e0ec14ab","MODULE_LICENSE_APACHE2":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","cargo_embargo.json":"958f9a85753bf1fa1d9dbd47b92221018d717240f25a8bf606d88b24bcd30416","rules.mk":"6ee7ab0e0d5dc019a7e45323d5799800e3f772fead77acc1a53018c13aa44b3d","src/ast/attrs.rs":"5ac3980b23f06ac06b16496c9901a607b885b4885c3054d3b09606946ea50681","src/ast/docs.rs":"88e00220085a483cb654b941e0690a1dd873255f59ffda273e665a2b989874de","src/ast/enums.rs":"8dd933a376b4eff09e1e220668017ebe16f7cf4e09efcfdd5159d5ddcdf00fd6","src/ast/idents.rs":"d2cb3fa5d145d1636118dd382c1471343d7e7c689b0dda87c03468625f0543b5","src/ast/lifetimes.rs":"3bd41dc9b39705e7bf69daf95e5d22da9e0eb7e77d07ad325a61832da60b49d7","src/ast/methods.rs":"4028caeb4766ad20c8ecc7c09adb813bd5fbb221db7e210140987adbfbafc381","src/ast/mod.rs":"fa4f6e4bfb293ce236e2707aa23536a254c0e1842c06447e6cef32d3ef5f75ae","src/ast/modules.rs":"5f7c7233273c20731b5142ef515559f83cebade05f4536e4886ff843c854aea6","src/ast/paths.rs":"a033da3954916f6b4a24c4e382576b32be93cee1cd1dfa372d9c4f20156a9864","src/ast/snapshots/diplomat_core__ast__attrs__tests__attr.snap":"d560f6ccba0c26c2fa6f372f28384464194634c06827f82c38209b935d0bd699","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-2.snap":"07a7758d9124039fac763b0ec832ada7ff68b7a54dadb4445293a9369160821e","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-3.snap":"6ae9d8505edd71fd026ad9e6e3382838647dde47e1ed86fafd52a7d19449bd33","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-4.snap":"6ae9d8505edd71fd026ad9e6e3382838647dde47e1ed86fafd52a7d19449bd33","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-5.snap":"f8348506c857981e5cd1cff690215b46fbfc097b12e2a7a9e5adaf4542b57dc4","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs.snap":"be8afaaf7346e69e7653db91c5b222af1a4c54ce0ca0341c1581dc4fca184be4","src/ast/snapshots/diplomat_core__ast__attrs__tests__rename-2.snap":"38d636a1f5cc48fc4655b30b47753919a65dbe64bb343c51f0db0389a30f2c84","src/ast/snapshots/diplomat_core__ast__attrs__tests__rename.snap":"38d636a1f5cc48fc4655b30b47753919a65dbe64bb343c51f0db0389a30f2c84","src/ast/snapshots/diplomat_core__ast__enums__tests__enum_with_discr.snap":"49822af14e8898641e5af907dc046e3d6abff1784ae2e5f0948a628f462f23a1","src/ast/snapshots/diplomat_core__ast__enums__tests__simple_enum.snap":"11d78b8b06a5b021dfde2420e718c2c86ab11a8781ac8a7d07ebd9b14d5b9602","src/ast/snapshots/diplomat_core__ast__methods__tests__cfged_method.snap":"4b583a1626f65d77b51ba30b18b786db5ef4499ff269eac38542e9defe1d72f1","src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods-2.snap":"35843a16e1bb2c9943ca14e0f6366a6d548f325f9e76796523dea6eec8b8e572","src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods.snap":"9f3b1e88def2e947b39c78c98d9f0bf14c8ca7f050fb07c9a6dbff0ce9cfcf9f","src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods-2.snap":"38c82dd5a11ceedbe48fedd42ed1946029c246e9e7efa04326e8e3feacd8cda4","src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods.snap":"9778bcfd3921df391c4ac06c6c9c2209276655ec14713635457056c19fbd5be4","src/ast/snapshots/diplomat_core__ast__modules__tests__import_in_non_diplomat_not_analyzed.snap":"2b3c0a5bebbf397e9c25638a4ed05c6eab16a101b52a0aa486d83fba9adc0720","src/ast/snapshots/diplomat_core__ast__modules__tests__method_visibility.snap":"98ff27c487b3fc1972f30bcbbac339139f9af94a6575102c8bee34e187500e01","src/ast/snapshots/diplomat_core__ast__modules__tests__simple_mod.snap":"90ddddbabbe40622fe91ef8da91a11dc260c82f766ddf4df2d6f032cce711cf7","src/ast/snapshots/diplomat_core__ast__structs__tests__simple_struct.snap":"96768de34fcd8ea6f97f46e4640a0352bf11b916f7943fe14f7c9e0021006a8c","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-2.snap":"b9129f8e4a73e534e2a4fcad1f6bfd4173383273a9e5e837a075bb4017f41e0c","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-3.snap":"01d174928534003996e696954703aa93b1d2f56ea3959b37b9954a25456fe638","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-4.snap":"21876d9a6ee712c393bfff33d88411cfe58e70cd67e1f6b543c7831948e75386","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-5.snap":"e5fadef9bfcd741f74fc02fe20c5ae34637accdc9eba2eb3c0d9f8109b1ac408","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-6.snap":"97bbeeb1cd9efc7ed419ee945a878c0be3841d4b477360ead875328fdb29c739","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-7.snap":"60f8e15979600a1fa9b0c7efec3068a57a2d9e5d9231088b2922fdc92811d26d","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes.snap":"f11b2e4daa8a7229b4fbc4dbac6816834b5367f4081b3d4c516a3bf99f5db76b","src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes-2.snap":"b2caf0ebb1085e65cb74cb2ee21cba8f5450e39309033da714d1147f55e581f0","src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes.snap":"ea241a0ed44e38134e24885382d060834536fcd8390e8924f3689cd855c81695","src/ast/snapshots/diplomat_core__ast__types__tests__typename_named.snap":"0f6e75b41366ba31a42a4c86e74b8ec48b8e7ab105a1360027a6798f6847df97","src/ast/snapshots/diplomat_core__ast__types__tests__typename_option-2.snap":"afb1c4876eb82939a122d64a507a0b5f3ca4388120ea867ffefeca8aa0419caf","src/ast/snapshots/diplomat_core__ast__types__tests__typename_option.snap":"cf8a34f9a60e7e4ac8c2eeb0b7c62847482309d14ed44c040bb8f138f529f526","src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-2.snap":"0b9230ce53d4db086ac4976867ebd093a080e209ef301d30ec08935b48bb6896","src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-3.snap":"6f0ab0393da6b01f42bad1e58e3702ce195346770cae3d8210b29fec8828c283","src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives.snap":"ee01e60f3b3d732ee4cba03be0c79bb8d2a5a4b12ba4127cddd2fc2727a27a15","src/ast/snapshots/diplomat_core__ast__types__tests__typename_references-2.snap":"151fc890399b2f1d22a14903105117483364999d4306ca90a3c4f9120ad1a49d","src/ast/snapshots/diplomat_core__ast__types__tests__typename_references.snap":"be5f3ae253861195227e0bd7fc6221449d7a6240cc71ca5e8e9130aaac5e4cca","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-2.snap":"5fec5ee1501c4550eaa7c1ce235295f371cbe17333fb029a1f678f877f911c1e","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-3.snap":"52f970e28ba7839db1693726443a299c7f932eedefad89a885a191645f06b830","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-4.snap":"320f11fda8d120def05728fd6dd2e84042bfa1a8195caa94142640c6b9be2fa4","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result.snap":"c560eb23c0b5e67344e7588b507c1aa548381ca78a989ba41a5d6d37b3a44d91","src/ast/snapshots/diplomat_core__ast__validity__tests__lifetime_in_return.snap":"c89b6c0cca5bd544214d7b8ff473d969213289e11ca7f7b6d08b6f51503e4468","src/ast/structs.rs":"8cc878f1d90bc806ae18f3655cad69a803b4b334efd8c1ffe4cf0656ae927085","src/ast/traits.rs":"84a8e393a858d8b73664842d9a3c2b595e0f157fc83333de02e688e0502931c7","src/ast/types.rs":"ce8454682d3d6836d1c5d4bcab6d7897912b5ca83d71d528b68d9b485f397796","src/environment.rs":"eb3027ab865457d0253e742ca52f674acee99f72d45a983b33c6485f42633589","src/hir/attrs.rs":"eb8368b34a25c26f2bbd396a73e1a2335d66c49ad975956ec6dc031c1fdcee8f","src/hir/defs.rs":"04ca2d631ac5917e3fbf618d9d154db88fe2cc73be9dd487289b77367b77a9e8","src/hir/elision.rs":"b2496e8ad480562ac9cdbc9d339b98fc8ec2e2fdff0051b4ff9c9124e9067d3d","src/hir/lifetimes.rs":"a8552091663aac9af38b4e1c24e0fe23e55af0ef3db0c72da60681cd79b33e1f","src/hir/lowering.rs":"1e3f0ed267469bed78218efad7b6f0361647f42a39f81e806e1e3c5fbd5dbad3","src/hir/methods.rs":"cb5dc3ebd7252efe3ab96b5e88db0a586dc5bcee43905c2c4a16bc40311c9272","src/hir/methods/borrowing_field.rs":"8d4b8eab3cb161ddb771d327cf98b66396d5de40db7e20cb8beead58aed7344c","src/hir/methods/borrowing_param.rs":"41c2b0111d655226a55daa093363842076eae70413f1599dbd92d78822a40cb6","src/hir/mod.rs":"216ea1d3ce3cb2a7127ecc50f43aef6c3a44a2dd089882b7d995c2f9cea8ddf6","src/hir/paths.rs":"0fcbb459e92cf0acd9444669c46cd17490b60c83268c400e24b8703b7e851623","src/hir/primitives.rs":"93d52f6ef496051c1561f9d00d58b462bfb752085dd3dfd84d310ff1ed3180ac","src/hir/snapshots/diplomat_core__hir__attrs__tests__auto.snap":"2cf071682c620e836da60aa88f8f7d3ab795f243b14992001a0f37ef4c35fc11","src/hir/snapshots/diplomat_core__hir__attrs__tests__comparator.snap":"c8708974280e72957d05601f69c008590eca30f5c8344a7f9e68bd4f5ea1bd9f","src/hir/snapshots/diplomat_core__hir__attrs__tests__iterator.snap":"cb0a8e763a9799d9814626109105e308f25ba421e72824f7217a0f5cd0518e34","src/hir/snapshots/diplomat_core__hir__attrs__tests__unsupported_features.snap":"a1167949471515c81fe369ebf69442ec79dadf7fd580dad828e313a5e38b5d85","src/hir/snapshots/diplomat_core__hir__elision__tests__borrowing_fields.snap":"e320338939daed60f8c5af2017a3ff4757224437aefebb9dbafc2bc2e014fa3f","src/hir/snapshots/diplomat_core__hir__elision__tests__elision_in_struct.snap":"a5086b6d646823431937618a7027c707b4ae8183835e5a9be85e91e5d10f4d33","src/hir/snapshots/diplomat_core__hir__elision__tests__simple_mod.snap":"fbdc0c10bacfdd69a5ca51d7dd9c132f159ecf6be15c258bb7556a9fe4950654","src/hir/snapshots/diplomat_core__hir__type_context__tests__basic_lowering.snap":"2c301b9cf25f8e66566c8ea19121199a8b6dfb57e03c3d556a1447d2d08de8c9","src/hir/snapshots/diplomat_core__hir__type_context__tests__lifetime_in_return.snap":"7b10b9def24b0435d6b3889156dd7bda9706afbc0d0e26b821e8f2f057ce7faf","src/hir/snapshots/diplomat_core__hir__type_context__tests__non_opaque_move.snap":"86b475c55ab1130901b52a08438eea69f066057b79c35e34c64d3ca26182d45e","src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_error.snap":"a501cdd3f1869fe6977166c9a25c78ba113d169dbe396c7ef28d2e9cda514795","src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_safe_use.snap":"f0798b0c35a9737e22fd12982aa8103a16cf41770c3bfec0ee8275202bb5fbe4","src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap":"dbaa6e4bafb2ad2e371b4cd2f3a174dfa54879e956eacc8adc832fc1ecadd21d","src/hir/snapshots/diplomat_core__hir__type_context__tests__option.snap":"35f6f13ccc2a9422df499cd7dcc88d27afbfa8f60e3cd3a781eb1cd251e9564c","src/hir/snapshots/diplomat_core__hir__type_context__tests__option_invalid.snap":"e58a6c2fa6121e6b865d5bed39a8014651e152c5bd86fc53b5f3dc318bffd8e5","src/hir/snapshots/diplomat_core__hir__type_context__tests__option_valid.snap":"de35f4688b545c2069d6e21f2a7de61ac91fb9c9833fd2175b5dd9d9927afbd0","src/hir/snapshots/diplomat_core__hir__type_context__tests__required_implied_bounds.snap":"78b3d1af6a11e9c7716352ce609b8d8595cf9b197ec75d6d8adb7fb02723f91d","src/hir/snapshots/diplomat_core__hir__type_context__tests__struct_forbidden.snap":"705c90764215473972a8dcb1a204659ab58d71872c73470da8b50d3e0c54d188","src/hir/snapshots/diplomat_core__hir__type_context__tests__zst_non_opaque.snap":"a0b3d437ff0eb6ca44a1147fe1df5418e019366b0af15e8338e273c4400539f5","src/hir/ty_position.rs":"c21f94a375301c8485d35ae47a5a0bc35c32e8c07c025ccc5bee079bf03278c6","src/hir/type_context.rs":"b6f3dd0672062906be2bfeea4d1d211e8fafdadcdf115b1d795ffd3c0e83cd35","src/hir/types.rs":"bd20ac65fad73819ed6db1b74b6b704c384fc62f7e67cfb62775589c26f3d178","src/lib.rs":"7223d5d1973615128a2e6eb39deb6eeb7c5ff9c206371fd6abca69f4b30417fb"}}
\ No newline at end of file
diff --git a/crates/diplomat_core/.cargo-checksum.json b/crates/diplomat_core/.cargo-checksum.json
new file mode 100644
index 0000000..30bdd09
--- /dev/null
+++ b/crates/diplomat_core/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"99c49d34e32f5be3057736911087705b3a049c7018d2ac5c71e776e619a85ff9","LICENSE-APACHE":"639c20c7f14fb122750d5ad1a6cfb116d9bf8d103e709ee40949e5a12a731666","LICENSE-MIT":"3337fe6e4a3830ad87c23cb9d6d750f9a1e5c45efc08de9c76c1a207fc6966c4","src/ast/attrs.rs":"e666895ad0adb5fd38f622583a4d1c27e64e08fdbaf9119e88e106a1179abd9e","src/ast/docs.rs":"40a8d0b6efa46a20c92cbabd3df786b286b6279a532855ad83d9b2097d420f88","src/ast/enums.rs":"ca2d018a3cf3cb6d0b075d2c8f19015e870a050cd5d1f0ae730738e9b0e4b138","src/ast/idents.rs":"05c7245ce6cb0d46b070f92a4996247545e1699da66d546bfd039a1e3a1a717d","src/ast/lifetimes.rs":"472ceed18ae7d74c0cef200274d677b8dd57b2cda938de111ce6fd619aadbf7b","src/ast/methods.rs":"4b889c34c8029909b1eda2bc6b764d5ac5feb720b45ba34baad38538570a2643","src/ast/mod.rs":"a398ae20d74cd36c18805c8d7b48ab668746f7a3aadeba6a3dcf15b5322687cb","src/ast/modules.rs":"5638e1ef521a96daf52ae2cf1d0aee8d9d15554ee2ffc5abd1708d30ed29b6b8","src/ast/paths.rs":"480bef69cb395eb931a7e086ccde7b49f508ace10e61367002c4b072b9dd4b44","src/ast/snapshots/diplomat_core__ast__attrs__tests__attr.snap":"a67d1d88995e5140fae1b85175885c79e833a6c09d30df164bb800cf70bb9725","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-2.snap":"1fbb268bb55cad81803555e0552948bfa9ee1edc161f76b0e26bb5c16ac8389b","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-3.snap":"ed4ba8423cb7605fd7a5e3faf35baafa58ce6758b50da2c98ccc0cd60792bfff","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-4.snap":"ed4ba8423cb7605fd7a5e3faf35baafa58ce6758b50da2c98ccc0cd60792bfff","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-5.snap":"2cbfa3d75adb944b19a4e0abdfa5cf7f9a4f82cdcd4d4b0efdbf1c857dc12aaf","src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs.snap":"0f42d7d22c82d1950aea7a59ead36701d6d572efe0d0969608afa58d8780e052","src/ast/snapshots/diplomat_core__ast__attrs__tests__rename-2.snap":"e790b3af120d71bb30b89b3a9b803cb42c58cf2f77101517cc3aa718497aa65c","src/ast/snapshots/diplomat_core__ast__attrs__tests__rename.snap":"e790b3af120d71bb30b89b3a9b803cb42c58cf2f77101517cc3aa718497aa65c","src/ast/snapshots/diplomat_core__ast__enums__tests__enum_with_discr.snap":"b89a5ec4a27f4b37a9579e177bede43fa6d453dd613ba4bc36e8e8e85ec92e7e","src/ast/snapshots/diplomat_core__ast__enums__tests__simple_enum.snap":"ff5ff54221eb87d980ed7bc1d1377b335b918dcea53caa3f4b9345b2e4b105f4","src/ast/snapshots/diplomat_core__ast__methods__tests__cfged_method.snap":"c40532110650d39deec501fdff4fe69d9513adb40a787eff60391926918f2b04","src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods-2.snap":"af48687633e88c1de24495b7dc8d6cbdf2972e9316e0d3f83fbe1f6686595634","src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods.snap":"1ad9b01794280d738c76fc4dc2bd561f7177eb93855ff37d6ddc40d3e587c48e","src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods-2.snap":"8af6ba37c419c4d9dc4e61a33f87585d25b0ef51716c216e0372f8c1952cf053","src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods.snap":"c9ea1881533ce39fa7d9dc9ccc0949db2040f1828d260a1bacac870dc3b84863","src/ast/snapshots/diplomat_core__ast__modules__tests__import_in_non_diplomat_not_analyzed.snap":"d3abb336a5ef69b2e2264a29c629fb21e38923274c4e1925bcdb45036d568178","src/ast/snapshots/diplomat_core__ast__modules__tests__method_visibility.snap":"d9f6d2a0e2fad3234286a9059864c10a430e4fb52d7f02e30cf28e8fd7adc89b","src/ast/snapshots/diplomat_core__ast__modules__tests__simple_mod.snap":"036eb71424c981ce651daf118f378977ef1d9a12f044f14257954e6c6dabea15","src/ast/snapshots/diplomat_core__ast__structs__tests__simple_struct.snap":"f0b5e2a7da2fe0e50d1b123b2b746891b35cbd79d6de1394217007d90cc2fa0d","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-2.snap":"75c377161d51dc0c4d7d91b59b04c1de62b5dffaa1a2c9cac7841ce359f82d9d","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-3.snap":"418ff306adad93373b277f1a2fa02d1c9e66c54534d2f675288e05dcdec9696f","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-4.snap":"f7bad0025bf2c64a18e8dd7d4a5c43f2b5ccbd4ff88e9bd2f78c226fb3f22540","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-5.snap":"9a7b7dde9399f300bfcd739506ded5113021880629d75291b962984ec1e8fe8c","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-6.snap":"ff1e3b2867786c467ef1148d2837da441a23e4b32d7a284a438ff5c317bafc81","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-7.snap":"291a10613d0128d3b4cf9ef4eeea0ca43d5266c539f9a8ff130addef140bacac","src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes.snap":"6306fae6bc6fbaf5eb679bf3313877259f9ccc09232da568fd98fe8e20eab284","src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes-2.snap":"97946b1e3118aec7ac7490f0bf38ba45013bb63c5d6bac12e3dd20fc7103348f","src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes.snap":"e3a1a07e2bfa0869918c973983b658a8e4123203f86a54a743dc6aff6a984f65","src/ast/snapshots/diplomat_core__ast__types__tests__typename_named.snap":"30b58ef9e59641eea9dfba9b965256d7a5d8667e241b7a209eb7d09208d75f7e","src/ast/snapshots/diplomat_core__ast__types__tests__typename_option-2.snap":"c90606884d4100b2d1c13822452743df2d771eec673c181eaf52808b0ba438e6","src/ast/snapshots/diplomat_core__ast__types__tests__typename_option.snap":"b60052fe5d5d553e8c9ed9995ffefbb8d0a414c567c8a58675c3cc5394392d73","src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-2.snap":"506dcd6ea86644d4043229804bf615c8bc34cd20feef2204cd755674bf3cd00f","src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-3.snap":"b92cd7f7cb2877c83ed8fac16d27b670e0e9d501cd6b3f70db84b322b686405f","src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives.snap":"c086765d65faac19f529a03f50ca0566cfe4e9a10b32f608ae4b9c3ad27ffdad","src/ast/snapshots/diplomat_core__ast__types__tests__typename_references-2.snap":"3ced7f6c3e2078d9534dff84e5e4a200efa84bf94435fde478c9e02b4a2b2053","src/ast/snapshots/diplomat_core__ast__types__tests__typename_references.snap":"77d5bcd560adff2216e349e0c87cff988501aef22848fbf1906b6b622ffe47cf","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-2.snap":"aafb55a23e6be26ea03ece0595e0fa0c451bdba51f606ecaa2cddea7b21cd642","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-3.snap":"5cc176444520a4ebf76c184d4c33643d83a87f18a4856b2565d41de79798e831","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-4.snap":"64addbbcc9efce4ff6b98856154ce695271dc9dd74e9d921b35c2b0cf55e4bbe","src/ast/snapshots/diplomat_core__ast__types__tests__typename_result.snap":"d67d6adbd0d814503f4bcde00d65ee3e7dd353a4a6cf96b4206f145e46fbcddb","src/ast/snapshots/diplomat_core__ast__validity__tests__lifetime_in_return.snap":"aa05830ac368b2d10bc9403aaa2c8afc2345a2e21ff86e2089a0e664fde16254","src/ast/structs.rs":"6f3137e7dfd82621f9cc03c48c30a078939b274e64a528f46d4f29e82bb67146","src/ast/traits.rs":"edc878c09b52bc28dc59ed799eab4be11775a6c6625f5c075c521b4b4969f124","src/ast/types.rs":"acec39566d9e7ba9ac852d97e711e7608b156aaead2c1ded4bcad75eedc2a398","src/environment.rs":"85005a4afabd0b1c22ca17d7ba12ef71783e9c001359037c714e13788f3e39a4","src/hir/attrs.rs":"76cbf095e79b096d66bc3c2dcf491e455af4b7ec2efe9a89bedc5e3ffd7a6793","src/hir/defs.rs":"e69c37ecba56d8aa3dd73659132fd568cd37786f671bd3519aa4395cb490772c","src/hir/elision.rs":"0500b3ce8f064ccb255078c9232b8195d399d9a17a5988be8fb823a2e07b5d08","src/hir/lifetimes.rs":"f816dcc83401f027c602eab3111b1eac02b1f7b80efdc2a1ad20aa58bd460c9c","src/hir/lowering.rs":"d383584ad424ae5459671486ece82fe4d2a227eb029efe5b30aff9ee0d4f3b97","src/hir/methods.rs":"3e3e47bb6c751d62afd4158c6cafcd162b93a54ee4c874d0fcb572c052d39cd8","src/hir/methods/borrowing_field.rs":"f2b478e116ce5417875745271465650156296658b8b85de4b2f191268cf62f1d","src/hir/methods/borrowing_param.rs":"7a98fe6bc2918a22e3530172629fe8bc1097e3ae37fa7f066f3fc1a014070807","src/hir/mod.rs":"04cbd8a20366e6954384dc897b1493e9666d3e9d5f65040c3dfedafd3ffb3f83","src/hir/paths.rs":"917e63a413c97c0a8a3763fa38ef29ac2b75464171d4432bac7dc748fdcbe276","src/hir/primitives.rs":"863200474471f7bbedd52756949625d929f7bee11bd5baf95bc4bc57dce41fe1","src/hir/snapshots/diplomat_core__hir__attrs__tests__auto.snap":"6b564da738c33c30d5fa2d980e963ff5ceaee3f6e225bbaed46fdd57ffc09b52","src/hir/snapshots/diplomat_core__hir__attrs__tests__comparator.snap":"e3e722be30320849899344aa96b9635c6918ac0579b48e43952908a6d5a81cd9","src/hir/snapshots/diplomat_core__hir__attrs__tests__iterator.snap":"a38579d2737691b3ce6185fd6fc3398210e8bf115add9ba186a80d77684dd940","src/hir/snapshots/diplomat_core__hir__attrs__tests__unsupported_features.snap":"35dc32635b7b55cb877d4c3158e56352ef54de6ccb95ebe03d915c2c8bc71df4","src/hir/snapshots/diplomat_core__hir__elision__tests__borrowing_fields.snap":"6c2a9c1135f6259df281ed47760469849f21858be7c02caa4d47bcd77fc51f9a","src/hir/snapshots/diplomat_core__hir__elision__tests__elision_in_struct.snap":"0aa039c732a81c74e41e2ff8d0c32976a2cc89c1f8559a12aea6de743a97dc71","src/hir/snapshots/diplomat_core__hir__elision__tests__simple_mod.snap":"731ae15583212d5a558bf0b3bb62442fbf48803c10f47e067e0db14d6ec0aad9","src/hir/snapshots/diplomat_core__hir__type_context__tests__basic_lowering.snap":"c3f453168b41a7268e6a3b022cb1c33822962df9fa683395ce759a8db4dd9714","src/hir/snapshots/diplomat_core__hir__type_context__tests__lifetime_in_return.snap":"6729419ac3c2fc2132dc8a7c12ed6b1383657ce33538a4d12b89fe73d65b036b","src/hir/snapshots/diplomat_core__hir__type_context__tests__non_opaque_move.snap":"dfc9bcce400229c476381b135958ffbc5b4850bdf36c3aa059a96bc28d627680","src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_error.snap":"1c9a5bffce9ccf66732caf6b379a6523eff53e533ebf4adbecfb169b2a384d60","src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_safe_use.snap":"c3dee2afc274a8b70bf686ad9970826d5b59b798eecabb70a4b7bdb272816bab","src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap":"37d1f25e0a479e960194f2ab351ee7977e35def2354501713ad6fbf3a98670c0","src/hir/snapshots/diplomat_core__hir__type_context__tests__option.snap":"1bf15a0b08a68742cb9f8a16e0aa1157596faf7c5e24e01e529633864a30efa9","src/hir/snapshots/diplomat_core__hir__type_context__tests__option_invalid.snap":"608a711234cd1c19d0faa750065d4eb4f3357ad1f49e06f24260ad91b7721a95","src/hir/snapshots/diplomat_core__hir__type_context__tests__option_valid.snap":"d29eabf19445e75454532259015011b06ec37d9cc9209476360d75bd75c61bfb","src/hir/snapshots/diplomat_core__hir__type_context__tests__required_implied_bounds.snap":"67bb19010f4db7eeb8261c7a6d31f59107340507d525f2a993a0485aff06519b","src/hir/snapshots/diplomat_core__hir__type_context__tests__struct_forbidden.snap":"36852df51a118656dbd474858d6466ab803c0ac2a17b80e77c52a319b791298c","src/hir/snapshots/diplomat_core__hir__type_context__tests__zst_non_opaque.snap":"e78ca15abab15a89da23c6f8f08bd6534331e7ca37e599061150615b1c674fbf","src/hir/ty_position.rs":"1a8031ba91796ece482570df20130604ac69cad4b499e0ae2244d6e6bf26962f","src/hir/type_context.rs":"ed8947e6591541501a1eb4063e0fe52e8e125377c822f8a2b9458cd6fd4f9591","src/hir/types.rs":"663c327f1b93733280f9cf4947f8839581e5fc14c9242b262f15fcb018c0a941","src/lib.rs":"6bca84ef449b737270c0da7e8ae9dc82cfbd57e83c078b20c9894050274ed7e7"},"package":"58e5ba87fee6b8b9dcc575cfbc84ae97b8b9f891fa27f670996a4684e20bd178"}
\ No newline at end of file
diff --git a/crates/diplomat_core/Android.bp b/crates/diplomat_core/Android.bp
new file mode 100644
index 0000000..711ff1c
--- /dev/null
+++ b/crates/diplomat_core/Android.bp
@@ -0,0 +1,39 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+ default_applicable_licenses: ["external_rust_crates_diplomat_core_license"],
+ default_team: "trendy_team_android_rust",
+}
+
+license {
+ name: "external_rust_crates_diplomat_core_license",
+ visibility: [":__subpackages__"],
+ license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+ license_text: ["LICENSE"],
+}
+
+rust_library_host {
+ name: "libdiplomat_core",
+ host_cross_supported: false,
+ crate_name: "diplomat_core",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.9.0",
+ crate_root: "src/lib.rs",
+ edition: "2021",
+ rustlibs: [
+ "libproc_macro2",
+ "libquote",
+ "libserde",
+ "libsmallvec",
+ "libstrck",
+ "libsyn",
+ ],
+ compile_multilib: "first",
+}
+
+dirgroup {
+ name: "trusty_dirgroup_external_rust_crates_diplomat_core",
+ visibility: ["//trusty/vendor/google/aosp/scripts"],
+ dirs: ["."],
+}
diff --git a/crates/diplomat_core/Cargo.toml b/crates/diplomat_core/Cargo.toml
new file mode 100644
index 0000000..df807f7
--- /dev/null
+++ b/crates/diplomat_core/Cargo.toml
@@ -0,0 +1,89 @@
+# 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"
+rust-version = "1.71"
+name = "diplomat_core"
+version = "0.9.0"
+authors = [
+ "Shadaj Laddad <shadaj@users.noreply.github.com>",
+ "Manish Goregaokar <manishsmail@gmail.com>",
+ "Quinn Okabayashi <QnnOkabayashi@users.noreply.github.com>",
+]
+build = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "Shared utilities between Diplomat macros and code generation"
+readme = false
+keywords = [
+ "ffi",
+ "codegen",
+]
+categories = [
+ "development-tools",
+ "compilers",
+]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-diplomat/diplomat"
+
+[package.metadata.docs.rs]
+all-features = true
+
+[lib]
+name = "diplomat_core"
+path = "src/lib.rs"
+
+[dependencies.displaydoc]
+version = "0.2"
+optional = true
+
+[dependencies.either]
+version = "1.9.0"
+optional = true
+default-features = false
+
+[dependencies.proc-macro2]
+version = "1.0.27"
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.serde]
+version = "1.0"
+features = [
+ "derive",
+ "alloc",
+]
+default-features = false
+
+[dependencies.smallvec]
+version = "1.9.0"
+
+[dependencies.strck]
+version = "1.0"
+features = ["ident"]
+
+[dependencies.syn]
+version = "2"
+features = [
+ "full",
+ "extra-traits",
+]
+
+[dev-dependencies.insta]
+version = "1.7.1"
+features = ["yaml"]
+
+[features]
+hir = ["either"]
diff --git a/crates/diplomat_core/LICENSE b/crates/diplomat_core/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/crates/diplomat_core/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE
\ No newline at end of file
diff --git a/crates/diplomat_core/LICENSE-APACHE b/crates/diplomat_core/LICENSE-APACHE
new file mode 100644
index 0000000..05fcffa
--- /dev/null
+++ b/crates/diplomat_core/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright 2022 The Diplomat Developers
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/crates/diplomat_core/LICENSE-MIT b/crates/diplomat_core/LICENSE-MIT
new file mode 100644
index 0000000..cf62c31
--- /dev/null
+++ b/crates/diplomat_core/LICENSE-MIT
@@ -0,0 +1,27 @@
+MIT License
+
+Copyright (c) 2022 The Diplomat Developers
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/crates/diplomat_core/METADATA b/crates/diplomat_core/METADATA
new file mode 100644
index 0000000..6732ebd
--- /dev/null
+++ b/crates/diplomat_core/METADATA
@@ -0,0 +1,17 @@
+name: "diplomat_core"
+description: "Shared utilities between Diplomat macros and code generation"
+third_party {
+ version: "0.9.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2025
+ month: 1
+ day: 24
+ }
+ homepage: "https://crates.io/crates/diplomat_core"
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/diplomat_core/diplomat_core-0.9.0.crate"
+ version: "0.9.0"
+ }
+}
diff --git a/crates/diplomat_core/MODULE_LICENSE_APACHE2 b/crates/diplomat_core/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/crates/diplomat_core/MODULE_LICENSE_APACHE2
diff --git a/crates/diplomat_core/cargo_embargo.json b/crates/diplomat_core/cargo_embargo.json
new file mode 100644
index 0000000..fdb7cb5
--- /dev/null
+++ b/crates/diplomat_core/cargo_embargo.json
@@ -0,0 +1,10 @@
+{
+ "package": {
+ "diplomat_core": {
+ "device_supported": false,
+ "host_first_multilib": true
+ }
+ },
+ "run_cargo": false,
+ "generate_rulesmk": true
+}
\ No newline at end of file
diff --git a/crates/diplomat_core/src/ast/attrs.rs b/crates/diplomat_core/src/ast/attrs.rs
new file mode 100644
index 0000000..fda31b9
--- /dev/null
+++ b/crates/diplomat_core/src/ast/attrs.rs
@@ -0,0 +1,493 @@
+//! This module contains utilities for dealing with Rust attributes
+
+use serde::ser::{SerializeStruct, Serializer};
+use serde::Serialize;
+use std::borrow::Cow;
+use std::convert::Infallible;
+use std::str::FromStr;
+use syn::parse::{Error as ParseError, Parse, ParseStream};
+use syn::{Attribute, Expr, Ident, Lit, LitStr, Meta, MetaList, Token};
+
+/// The list of attributes on a type. All attributes except `attrs` (HIR attrs) are
+/// potentially read by the diplomat macro and the AST backends, anything that is not should
+/// be added as an HIR attribute ([`crate::hir::Attrs`]).
+///
+/// # Inheritance
+///
+/// Attributes are typically "inherited": the attributes on a module
+/// apply to all types and methods with it, the attributes on an impl apply to all
+/// methods in it, and the attributes on an enum apply to all variants within it.
+/// This allows the user to specify a single attribute to affect multiple fields.
+///
+/// However, the details of inheritance are not always the same for each attribute. For example, rename attributes
+/// on a module only apply to the types within it (others methods would get doubly renamed).
+///
+/// Each attribute here documents its inheritance behavior. Note that the HIR attributes do not get inherited
+/// during AST construction, since at that time it's unclear which of those attributes are actually available.
+#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
+#[non_exhaustive]
+pub struct Attrs {
+ /// The regular #[cfg()] attributes. Inherited, though the inheritance onto methods is the
+ /// only relevant one here.
+ pub cfg: Vec<Attribute>,
+ /// HIR backend attributes.
+ ///
+ /// Inherited, but only during lowering. See [`crate::hir::Attrs`] for details on which HIR attributes are inherited.
+ ///
+ /// During AST attribute inheritance, HIR backend attributes are copied over from impls to their methods since the HIR does
+ /// not see the impl blocks.
+ pub attrs: Vec<DiplomatBackendAttr>,
+
+ /// Renames to apply to the underlying C symbol. Can be found on methods, impls, and bridge modules, and is inherited.
+ ///
+ /// Affects method names when inherited onto methods.
+ ///
+ /// Affects destructor names when inherited onto types.
+ ///
+ /// Inherited.
+ pub abi_rename: RenameAttr,
+
+ /// For use by [`crate::hir::Attrs::demo_attrs`]
+ pub demo_attrs: Vec<DemoBackendAttr>,
+}
+
+impl Attrs {
+ fn add_attr(&mut self, attr: Attr) {
+ match attr {
+ Attr::Cfg(attr) => self.cfg.push(attr),
+ Attr::DiplomatBackend(attr) => self.attrs.push(attr),
+ Attr::CRename(rename) => self.abi_rename.extend(&rename),
+ Attr::DemoBackend(attr) => self.demo_attrs.push(attr),
+ }
+ }
+
+ /// Get a copy of these attributes for use in inheritance, masking out things
+ /// that should not be inherited
+ pub(crate) fn attrs_for_inheritance(&self, context: AttrInheritContext) -> Self {
+ // These attributes are inherited during lowering (since that's when they're parsed)
+ //
+ // Except for impls: lowering has no concept of impls so these get inherited early. This
+ // is fine since impls have no inherent behavior and all attributes on impls are necessarily
+ // only there for inheritance
+ let attrs = if context == AttrInheritContext::MethodFromImpl {
+ self.attrs.clone()
+ } else {
+ Vec::new()
+ };
+
+ let demo_attrs = if context == AttrInheritContext::MethodFromImpl {
+ self.demo_attrs.clone()
+ } else {
+ Vec::new()
+ };
+
+ let abi_rename = self.abi_rename.attrs_for_inheritance(context, true);
+ Self {
+ cfg: self.cfg.clone(),
+
+ attrs,
+ abi_rename,
+ demo_attrs,
+ }
+ }
+
+ pub(crate) fn add_attrs(&mut self, attrs: &[Attribute]) {
+ for attr in syn_attr_to_ast_attr(attrs) {
+ self.add_attr(attr)
+ }
+ }
+ pub(crate) fn from_attrs(attrs: &[Attribute]) -> Self {
+ let mut this = Self::default();
+ this.add_attrs(attrs);
+ this
+ }
+}
+
+impl From<&[Attribute]> for Attrs {
+ fn from(other: &[Attribute]) -> Self {
+ Self::from_attrs(other)
+ }
+}
+
+enum Attr {
+ Cfg(Attribute),
+ DiplomatBackend(DiplomatBackendAttr),
+ CRename(RenameAttr),
+ DemoBackend(DemoBackendAttr),
+ // More goes here
+}
+
+fn syn_attr_to_ast_attr(attrs: &[Attribute]) -> impl Iterator<Item = Attr> + '_ {
+ let cfg_path: syn::Path = syn::parse_str("cfg").unwrap();
+ let dattr_path: syn::Path = syn::parse_str("diplomat::attr").unwrap();
+ let crename_attr: syn::Path = syn::parse_str("diplomat::abi_rename").unwrap();
+ let demo_path: syn::Path = syn::parse_str("diplomat::demo").unwrap();
+ attrs.iter().filter_map(move |a| {
+ if a.path() == &cfg_path {
+ Some(Attr::Cfg(a.clone()))
+ } else if a.path() == &dattr_path {
+ Some(Attr::DiplomatBackend(
+ a.parse_args()
+ .expect("Failed to parse malformed diplomat::attr"),
+ ))
+ } else if a.path() == &crename_attr {
+ Some(Attr::CRename(RenameAttr::from_meta(&a.meta).unwrap()))
+ } else if a.path() == &demo_path {
+ Some(Attr::DemoBackend(
+ a.parse_args()
+ .expect("Failed to parse malformed diplomat::demo"),
+ ))
+ } else {
+ None
+ }
+ })
+}
+
+impl Serialize for Attrs {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ // 1 is the number of fields in the struct.
+ let mut state = serializer.serialize_struct("Attrs", 1)?;
+ if !self.cfg.is_empty() {
+ let cfg: Vec<_> = self
+ .cfg
+ .iter()
+ .map(|a| quote::quote!(#a).to_string())
+ .collect();
+ state.serialize_field("cfg", &cfg)?;
+ }
+ if !self.attrs.is_empty() {
+ state.serialize_field("attrs", &self.attrs)?;
+ }
+ if !self.abi_rename.is_empty() {
+ state.serialize_field("abi_rename", &self.abi_rename)?;
+ }
+ state.end()
+ }
+}
+
+/// A `#[diplomat::attr(...)]` attribute
+///
+/// Its contents must start with single element that is a CFG-expression
+/// (so it may contain `foo = bar`, `foo = "bar"`, `ident`, `*` atoms,
+/// and `all()`, `not()`, and `any()` combiners), and then be followed by one
+/// or more backend-specific attributes, which can be any valid meta-item
+#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
+#[non_exhaustive]
+pub struct DiplomatBackendAttr {
+ pub cfg: DiplomatBackendAttrCfg,
+ #[serde(serialize_with = "serialize_meta")]
+ pub meta: Meta,
+}
+
+fn serialize_meta<S>(m: &Meta, s: S) -> Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ quote::quote!(#m).to_string().serialize(s)
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
+#[non_exhaustive]
+pub enum DiplomatBackendAttrCfg {
+ Not(Box<DiplomatBackendAttrCfg>),
+ Any(Vec<DiplomatBackendAttrCfg>),
+ All(Vec<DiplomatBackendAttrCfg>),
+ Star,
+ // "auto", smartly figure out based on the attribute used
+ Auto,
+ BackendName(String),
+ NameValue(String, String),
+}
+
+impl Parse for DiplomatBackendAttrCfg {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(Ident) {
+ let name: Ident = input.parse()?;
+ if name == "auto" {
+ Ok(DiplomatBackendAttrCfg::Auto)
+ } else if name == "not" {
+ let content;
+ let _paren = syn::parenthesized!(content in input);
+ Ok(DiplomatBackendAttrCfg::Not(Box::new(content.parse()?)))
+ } else if name == "any" || name == "all" {
+ let content;
+ let _paren = syn::parenthesized!(content in input);
+ let list = content.parse_terminated(Self::parse, Token![,])?;
+ let vec = list.into_iter().collect();
+ if name == "any" {
+ Ok(DiplomatBackendAttrCfg::Any(vec))
+ } else {
+ Ok(DiplomatBackendAttrCfg::All(vec))
+ }
+ } else if input.peek(Token![=]) {
+ let _t: Token![=] = input.parse()?;
+ if input.peek(Ident) {
+ let value: Ident = input.parse()?;
+ Ok(DiplomatBackendAttrCfg::NameValue(
+ name.to_string(),
+ value.to_string(),
+ ))
+ } else {
+ let value: LitStr = input.parse()?;
+ Ok(DiplomatBackendAttrCfg::NameValue(
+ name.to_string(),
+ value.value(),
+ ))
+ }
+ } else {
+ Ok(DiplomatBackendAttrCfg::BackendName(name.to_string()))
+ }
+ } else if lookahead.peek(Token![*]) {
+ let _t: Token![*] = input.parse()?;
+ Ok(DiplomatBackendAttrCfg::Star)
+ } else {
+ Err(ParseError::new(
+ input.span(),
+ "CFG portion of #[diplomat::attr] fails to parse",
+ ))
+ }
+ }
+}
+
+/// Meant to be used with Attribute::parse_args()
+impl Parse for DiplomatBackendAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let cfg = input.parse()?;
+ let _comma: Token![,] = input.parse()?;
+ let meta = input.parse()?;
+ Ok(Self { cfg, meta })
+ }
+}
+
+// #region demo_gen specific attributes
+/// A `#[diplomat::demo(...)]` attribute
+#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize)]
+#[non_exhaustive]
+pub struct DemoBackendAttr {
+ #[serde(serialize_with = "serialize_meta")]
+ pub meta: Meta,
+}
+
+/// Meant to be used with Attribute::parse_args()
+impl Parse for DemoBackendAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let meta = input.parse()?;
+ Ok(Self { meta })
+ }
+}
+
+// #endregion
+
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub(crate) enum AttrInheritContext {
+ Variant,
+ Type,
+ /// When a method or an impl is inheriting from a module
+ MethodOrImplFromModule,
+ /// When a method is inheriting from an impl
+ ///
+ /// This distinction is made because HIR attributes are pre-inherited from the impl to the
+ /// method, so the boundary of "method inheriting from module" is different
+ MethodFromImpl,
+ // Currently there's no way to feed an attribute to a Module, but such inheritance will
+ // likely apply during lowering for config defaults.
+ #[allow(unused)]
+ Module,
+}
+
+/// A pattern for use in rename attributes, like `#[diplomat::abi_rename]`
+///
+/// This can be parsed from a string, typically something like `icu4x_{0}`.
+/// It can have up to one {0} for replacement.
+///
+/// In the future this may support transformations like to_camel_case, etc,
+/// probably specified as a list like `#[diplomat::abi_rename("foo{0}", to_camel_case)]`
+#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
+pub struct RenameAttr {
+ pattern: Option<RenamePattern>,
+}
+
+impl RenameAttr {
+ /// Apply all renames to a given string
+ pub fn apply<'a>(&'a self, name: Cow<'a, str>) -> Cow<'a, str> {
+ if let Some(ref pattern) = self.pattern {
+ let replacement = &pattern.replacement;
+ if let Some(index) = pattern.insertion_index {
+ format!("{}{name}{}", &replacement[..index], &replacement[index..]).into()
+ } else {
+ replacement.into()
+ }
+ } else {
+ name
+ }
+ }
+
+ /// Whether this rename is empty and will perform no changes
+ pub(crate) fn is_empty(&self) -> bool {
+ self.pattern.is_none()
+ }
+
+ pub(crate) fn extend(&mut self, other: &Self) {
+ if other.pattern.is_some() {
+ self.pattern.clone_from(&other.pattern);
+ }
+
+ // In the future if we support things like to_lower_case they may inherit separately
+ // from patterns.
+ }
+
+ /// Get a copy of these attributes for use in inheritance, masking out things
+ /// that should not be inherited
+ pub(crate) fn attrs_for_inheritance(
+ &self,
+ context: AttrInheritContext,
+ is_abi_rename: bool,
+ ) -> Self {
+ let pattern = match context {
+ // No inheritance from modules to method-likes for the rename attribute
+ AttrInheritContext::MethodOrImplFromModule if !is_abi_rename => Default::default(),
+ // No effect on variants
+ AttrInheritContext::Variant => Default::default(),
+ _ => self.pattern.clone(),
+ };
+ // In the future if we support things like to_lower_case they may inherit separately
+ // from patterns.
+ Self { pattern }
+ }
+
+ /// From a replacement pattern, like "icu4x_{0}". Can have up to one {0} in it for substitution.
+ fn from_pattern(s: &str) -> Self {
+ Self {
+ pattern: Some(s.parse().unwrap()),
+ }
+ }
+
+ pub(crate) fn from_meta(meta: &Meta) -> Result<Self, &'static str> {
+ let attr = StandardAttribute::from_meta(meta)
+ .map_err(|_| "#[diplomat::abi_rename] must be given a string value")?;
+
+ match attr {
+ StandardAttribute::String(s) => Ok(RenameAttr::from_pattern(&s)),
+ StandardAttribute::List(_) => {
+ Err("Failed to parse malformed #[diplomat::abi_rename(...)]: found list")
+ }
+ StandardAttribute::Empty => {
+ Err("Failed to parse malformed #[diplomat::abi_rename(...)]: found no parameters")
+ }
+ }
+ }
+}
+
+impl FromStr for RenamePattern {
+ type Err = Infallible;
+ fn from_str(s: &str) -> Result<Self, Infallible> {
+ if let Some(index) = s.find("{0}") {
+ let replacement = format!("{}{}", &s[..index], &s[index + 3..]);
+ Ok(Self {
+ replacement,
+ insertion_index: Some(index),
+ })
+ } else {
+ Ok(Self {
+ replacement: s.into(),
+ insertion_index: None,
+ })
+ }
+ }
+}
+
+#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize)]
+struct RenamePattern {
+ /// The string to replace with
+ replacement: String,
+ /// The index in `replacement` in which to insert the original string. If None,
+ /// this is a pure rename
+ insertion_index: Option<usize>,
+}
+
+/// Helper type for parsing standard attributes. A standard attribute typically will accept the forms:
+///
+/// - `#[attr = "foo"]` and `#[attr("foo")]` for a simple string
+/// - `#[attr(....)]` for a more complicated context
+/// - `#[attr]` for a "defaulting" context
+///
+/// This allows attributes to parse simple string values without caring too much about the NameValue vs List representation
+/// and then attributes can choose to handle more complicated lists if they so desire.
+pub(crate) enum StandardAttribute<'a> {
+ String(String),
+ List(#[allow(dead_code)] &'a MetaList),
+ Empty,
+}
+
+impl<'a> StandardAttribute<'a> {
+ /// Parse from a Meta. Returns an error when no string value is specified in the path/namevalue forms.
+ pub(crate) fn from_meta(meta: &'a Meta) -> Result<Self, ()> {
+ match meta {
+ Meta::Path(..) => Ok(Self::Empty),
+ Meta::NameValue(ref nv) => {
+ // Support a shortcut `abi_rename = "..."`
+ let Expr::Lit(ref lit) = nv.value else {
+ return Err(());
+ };
+ let Lit::Str(ref lit) = lit.lit else {
+ return Err(());
+ };
+ Ok(Self::String(lit.value()))
+ }
+ // The full syntax to which we'll add more things in the future, `abi_rename("")`
+ Meta::List(list) => {
+ if let Ok(lit) = list.parse_args::<LitStr>() {
+ Ok(Self::String(lit.value()))
+ } else {
+ Ok(Self::List(list))
+ }
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta;
+
+ use syn;
+
+ use super::{DiplomatBackendAttr, DiplomatBackendAttrCfg, RenameAttr};
+
+ #[test]
+ fn test_cfgs() {
+ let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(*);
+ insta::assert_yaml_snapshot!(attr_cfg);
+ let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(cpp);
+ insta::assert_yaml_snapshot!(attr_cfg);
+ let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(has = overloading);
+ insta::assert_yaml_snapshot!(attr_cfg);
+ let attr_cfg: DiplomatBackendAttrCfg = syn::parse_quote!(has = "overloading");
+ insta::assert_yaml_snapshot!(attr_cfg);
+ let attr_cfg: DiplomatBackendAttrCfg =
+ syn::parse_quote!(any(all(*, cpp, has="overloading"), not(js)));
+ insta::assert_yaml_snapshot!(attr_cfg);
+ }
+
+ #[test]
+ fn test_attr() {
+ let attr: syn::Attribute =
+ syn::parse_quote!(#[diplomat::attr(any(cpp, has = "overloading"), namespacing)]);
+ let attr: DiplomatBackendAttr = attr.parse_args().unwrap();
+ insta::assert_yaml_snapshot!(attr);
+ }
+
+ #[test]
+ fn test_rename() {
+ let attr: syn::Attribute = syn::parse_quote!(#[diplomat::abi_rename = "foobar_{0}"]);
+ let attr = RenameAttr::from_meta(&attr.meta).unwrap();
+ insta::assert_yaml_snapshot!(attr);
+ let attr: syn::Attribute = syn::parse_quote!(#[diplomat::abi_rename("foobar_{0}")]);
+ let attr = RenameAttr::from_meta(&attr.meta).unwrap();
+ insta::assert_yaml_snapshot!(attr);
+ }
+}
diff --git a/crates/diplomat_core/src/ast/docs.rs b/crates/diplomat_core/src/ast/docs.rs
new file mode 100644
index 0000000..c77fe5c
--- /dev/null
+++ b/crates/diplomat_core/src/ast/docs.rs
@@ -0,0 +1,423 @@
+use super::Path;
+use core::fmt;
+use quote::ToTokens;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use syn::parse::{self, Parse, ParseStream};
+use syn::{Attribute, Ident, Meta, Token};
+
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, Default)]
+pub struct Docs(String, Vec<RustLink>);
+
+impl Docs {
+ pub fn from_attrs(attrs: &[Attribute]) -> Self {
+ Self(Self::get_doc_lines(attrs), Self::get_rust_link(attrs))
+ }
+
+ fn get_doc_lines(attrs: &[Attribute]) -> String {
+ let mut lines: String = String::new();
+
+ attrs.iter().for_each(|attr| {
+ if let Meta::NameValue(ref nv) = attr.meta {
+ if nv.path.is_ident("doc") {
+ let node: syn::LitStr = syn::parse2(nv.value.to_token_stream()).unwrap();
+ let line = node.value().trim().to_string();
+
+ if !lines.is_empty() {
+ lines.push('\n');
+ }
+
+ lines.push_str(&line);
+ }
+ }
+ });
+
+ lines
+ }
+
+ fn get_rust_link(attrs: &[Attribute]) -> Vec<RustLink> {
+ attrs
+ .iter()
+ .filter(|i| i.path().to_token_stream().to_string() == "diplomat :: rust_link")
+ .map(|i| i.parse_args().expect("Malformed attribute"))
+ .collect()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty() && self.1.is_empty()
+ }
+
+ /// Convert to markdown
+ pub fn to_markdown(&self, docs_url_gen: &DocsUrlGenerator) -> String {
+ use std::fmt::Write;
+ let mut lines = self.0.clone();
+ let mut has_compact = false;
+ for rust_link in &self.1 {
+ if rust_link.display == RustLinkDisplay::Compact {
+ has_compact = true;
+ } else if rust_link.display == RustLinkDisplay::Normal {
+ if !lines.is_empty() {
+ write!(lines, "\n\n").unwrap();
+ }
+ write!(
+ lines,
+ "See the [Rust documentation for `{name}`]({link}) for more information.",
+ name = rust_link.path.elements.last().unwrap(),
+ link = docs_url_gen.gen_for_rust_link(rust_link)
+ )
+ .unwrap();
+ }
+ }
+ if has_compact {
+ if !lines.is_empty() {
+ write!(lines, "\n\n").unwrap();
+ }
+ write!(lines, "Additional information: ").unwrap();
+ for (i, rust_link) in self
+ .1
+ .iter()
+ .filter(|r| r.display == RustLinkDisplay::Compact)
+ .enumerate()
+ {
+ if i != 0 {
+ write!(lines, ", ").unwrap();
+ }
+ write!(
+ lines,
+ "[{}]({})",
+ i + 1,
+ docs_url_gen.gen_for_rust_link(rust_link)
+ )
+ .unwrap();
+ }
+ }
+ lines
+ }
+
+ pub fn rust_links(&self) -> &[RustLink] {
+ &self.1
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[non_exhaustive]
+pub enum RustLinkDisplay {
+ /// A nice expanded representation that includes the type name
+ ///
+ /// e.g. "See the \[link to Rust documentation\] for more details"
+ Normal,
+ /// A compact representation that will fit multiple rust_link entries in one line
+ ///
+ /// E.g. "For further information, see: 1, 2, 3, 4" (all links)
+ Compact,
+ /// Hidden. Useful for programmatically annotating an API as related without showing a link to the user
+ Hidden,
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, PartialOrd, Ord)]
+#[non_exhaustive]
+pub struct RustLink {
+ pub path: Path,
+ pub typ: DocType,
+ pub display: RustLinkDisplay,
+}
+
+impl Parse for RustLink {
+ fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
+ let path = input.parse()?;
+ let path = Path::from_syn(&path);
+ let _comma: Token![,] = input.parse()?;
+ let ty_ident: Ident = input.parse()?;
+ let typ = match &*ty_ident.to_string() {
+ "Struct" => DocType::Struct,
+ "StructField" => DocType::StructField,
+ "Enum" => DocType::Enum,
+ "EnumVariant" => DocType::EnumVariant,
+ "EnumVariantField" => DocType::EnumVariantField,
+ "Trait" => DocType::Trait,
+ "FnInStruct" => DocType::FnInStruct,
+ "FnInEnum" => DocType::FnInEnum,
+ "FnInTrait" => DocType::FnInTrait,
+ "DefaultFnInTrait" => DocType::DefaultFnInTrait,
+ "Fn" => DocType::Fn,
+ "Mod" => DocType::Mod,
+ "Constant" => DocType::Constant,
+ "AssociatedConstantInEnum" => DocType::AssociatedConstantInEnum,
+ "AssociatedConstantInTrait" => DocType::AssociatedConstantInTrait,
+ "AssociatedConstantInStruct" => DocType::AssociatedConstantInStruct,
+ "Macro" => DocType::Macro,
+ "AssociatedTypeInEnum" => DocType::AssociatedTypeInEnum,
+ "AssociatedTypeInTrait" => DocType::AssociatedTypeInTrait,
+ "AssociatedTypeInStruct" => DocType::AssociatedTypeInStruct,
+ "Typedef" => DocType::Typedef,
+ _ => {
+ return Err(parse::Error::new(
+ ty_ident.span(),
+ "Unknown rust_link doc type",
+ ))
+ }
+ };
+ let lookahead = input.lookahead1();
+ let display = if lookahead.peek(Token![,]) {
+ let _comma: Token![,] = input.parse()?;
+ let display_ident: Ident = input.parse()?;
+ match &*display_ident.to_string() {
+ "normal" => RustLinkDisplay::Normal,
+ "compact" => RustLinkDisplay::Compact,
+ "hidden" => RustLinkDisplay::Hidden,
+ _ => return Err(parse::Error::new(display_ident.span(), "Unknown rust_link display style: Must be must be `normal`, `compact`, or `hidden`.")),
+ }
+ } else {
+ RustLinkDisplay::Normal
+ };
+ Ok(RustLink { path, typ, display })
+ }
+}
+impl fmt::Display for RustLink {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}#{:?}", self.path, self.typ)
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, PartialOrd, Ord)]
+#[non_exhaustive]
+pub enum DocType {
+ Struct,
+ StructField,
+ Enum,
+ EnumVariant,
+ EnumVariantField,
+ Trait,
+ FnInStruct,
+ FnInEnum,
+ FnInTrait,
+ DefaultFnInTrait,
+ Fn,
+ Mod,
+ Constant,
+ AssociatedConstantInEnum,
+ AssociatedConstantInTrait,
+ AssociatedConstantInStruct,
+ Macro,
+ AssociatedTypeInEnum,
+ AssociatedTypeInTrait,
+ AssociatedTypeInStruct,
+ Typedef,
+}
+
+#[derive(Default)]
+pub struct DocsUrlGenerator {
+ default_url: Option<String>,
+ base_urls: HashMap<String, String>,
+}
+
+impl DocsUrlGenerator {
+ pub fn with_base_urls(default_url: Option<String>, base_urls: HashMap<String, String>) -> Self {
+ Self {
+ default_url,
+ base_urls,
+ }
+ }
+
+ fn gen_for_rust_link(&self, rust_link: &RustLink) -> String {
+ use DocType::*;
+
+ let mut r = String::new();
+
+ let base = self
+ .base_urls
+ .get(rust_link.path.elements[0].as_str())
+ .map(String::as_str)
+ .or(self.default_url.as_deref())
+ .unwrap_or("https://docs.rs/");
+
+ r.push_str(base);
+ if !base.ends_with('/') {
+ r.push('/');
+ }
+ if r == "https://docs.rs/" {
+ r.push_str(rust_link.path.elements[0].as_str());
+ r.push_str("/latest/");
+ }
+
+ let mut elements = rust_link.path.elements.iter().peekable();
+
+ let module_depth = rust_link.path.elements.len()
+ - match rust_link.typ {
+ Mod => 0,
+ Struct | Enum | Trait | Fn | Macro | Constant | Typedef => 1,
+ FnInEnum
+ | FnInStruct
+ | FnInTrait
+ | DefaultFnInTrait
+ | EnumVariant
+ | StructField
+ | AssociatedTypeInEnum
+ | AssociatedTypeInStruct
+ | AssociatedTypeInTrait
+ | AssociatedConstantInEnum
+ | AssociatedConstantInStruct
+ | AssociatedConstantInTrait => 2,
+ EnumVariantField => 3,
+ };
+
+ for _ in 0..module_depth {
+ r.push_str(elements.next().unwrap().as_str());
+ r.push('/');
+ }
+
+ if elements.peek().is_none() {
+ r.push_str("index.html");
+ return r;
+ }
+
+ r.push_str(match rust_link.typ {
+ Typedef => "type.",
+ Struct
+ | StructField
+ | FnInStruct
+ | AssociatedTypeInStruct
+ | AssociatedConstantInStruct => "struct.",
+ Enum
+ | EnumVariant
+ | EnumVariantField
+ | FnInEnum
+ | AssociatedTypeInEnum
+ | AssociatedConstantInEnum => "enum.",
+ Trait
+ | FnInTrait
+ | DefaultFnInTrait
+ | AssociatedTypeInTrait
+ | AssociatedConstantInTrait => "trait.",
+ Fn => "fn.",
+ Constant => "constant.",
+ Macro => "macro.",
+ Mod => unreachable!(),
+ });
+
+ r.push_str(elements.next().unwrap().as_str());
+
+ r.push_str(".html");
+
+ match rust_link.typ {
+ FnInStruct | FnInEnum | DefaultFnInTrait => {
+ r.push_str("#method.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ AssociatedTypeInStruct | AssociatedTypeInEnum | AssociatedTypeInTrait => {
+ r.push_str("#associatedtype.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ AssociatedConstantInStruct | AssociatedConstantInEnum | AssociatedConstantInTrait => {
+ r.push_str("#associatedconstant.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ FnInTrait => {
+ r.push_str("#tymethod.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ EnumVariant => {
+ r.push_str("#variant.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ StructField => {
+ r.push_str("#structfield.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ EnumVariantField => {
+ r.push_str("#variant.");
+ r.push_str(elements.next().unwrap().as_str());
+ r.push_str(".field.");
+ r.push_str(elements.next().unwrap().as_str());
+ }
+ _ => {}
+ }
+ r
+ }
+}
+
+#[test]
+fn test_docs_url_generator() {
+ let test_cases = [
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Struct)] },
+ "https://docs.rs/std/latest/std/foo/bar/struct.batz.html",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, StructField)] },
+ "https://docs.rs/std/latest/std/foo/struct.bar.html#structfield.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Enum)] },
+ "https://docs.rs/std/latest/std/foo/bar/enum.batz.html",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, EnumVariant)] },
+ "https://docs.rs/std/latest/std/foo/enum.bar.html#variant.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, EnumVariantField)] },
+ "https://docs.rs/std/latest/std/enum.foo.html#variant.bar.field.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Trait)] },
+ "https://docs.rs/std/latest/std/foo/bar/trait.batz.html",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, FnInStruct)] },
+ "https://docs.rs/std/latest/std/foo/struct.bar.html#method.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, FnInEnum)] },
+ "https://docs.rs/std/latest/std/foo/enum.bar.html#method.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, FnInTrait)] },
+ "https://docs.rs/std/latest/std/foo/trait.bar.html#tymethod.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, DefaultFnInTrait)] },
+ "https://docs.rs/std/latest/std/foo/trait.bar.html#method.batz",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Fn)] },
+ "https://docs.rs/std/latest/std/foo/bar/fn.batz.html",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Mod)] },
+ "https://docs.rs/std/latest/std/foo/bar/batz/index.html",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Constant)] },
+ "https://docs.rs/std/latest/std/foo/bar/constant.batz.html",
+ ),
+ (
+ syn::parse_quote! { #[diplomat::rust_link(std::foo::bar::batz, Macro)] },
+ "https://docs.rs/std/latest/std/foo/bar/macro.batz.html",
+ ),
+ ];
+
+ for (attr, expected) in test_cases.clone() {
+ assert_eq!(
+ DocsUrlGenerator::default().gen_for_rust_link(&Docs::from_attrs(&[attr]).1[0]),
+ expected
+ );
+ }
+
+ assert_eq!(
+ DocsUrlGenerator::with_base_urls(
+ None,
+ [("std".to_string(), "http://std-docs.biz/".to_string())]
+ .into_iter()
+ .collect()
+ )
+ .gen_for_rust_link(&Docs::from_attrs(&[test_cases[0].0.clone()]).1[0]),
+ "http://std-docs.biz/std/foo/bar/struct.batz.html"
+ );
+
+ assert_eq!(
+ DocsUrlGenerator::with_base_urls(Some("http://std-docs.biz/".to_string()), HashMap::new())
+ .gen_for_rust_link(&Docs::from_attrs(&[test_cases[0].0.clone()]).1[0]),
+ "http://std-docs.biz/std/foo/bar/struct.batz.html"
+ );
+}
diff --git a/crates/diplomat_core/src/ast/enums.rs b/crates/diplomat_core/src/ast/enums.rs
new file mode 100644
index 0000000..3169b63
--- /dev/null
+++ b/crates/diplomat_core/src/ast/enums.rs
@@ -0,0 +1,127 @@
+use serde::Serialize;
+
+use super::docs::Docs;
+use super::{AttrInheritContext, Attrs, Ident, Method};
+use quote::ToTokens;
+
+/// A fieldless enum declaration in an FFI module.
+#[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)]
+#[non_exhaustive]
+pub struct Enum {
+ pub name: Ident,
+ pub docs: Docs,
+ /// A list of variants of the enum. (name, discriminant, docs, attrs)
+ pub variants: Vec<(Ident, isize, Docs, Attrs)>,
+ pub methods: Vec<Method>,
+ pub attrs: Attrs,
+}
+
+impl Enum {
+ /// Extract an [`Enum`] metadata value from an AST node.
+ pub fn new(enm: &syn::ItemEnum, parent_attrs: &Attrs) -> Enum {
+ let mut last_discriminant = -1;
+ if !enm.generics.params.is_empty() {
+ // Generic types are not allowed.
+ // Assuming all enums cannot have lifetimes? We don't even have a
+ // `lifetimes` field. If we change our minds we can adjust this later
+ // and update the `CustomType::lifetimes` API accordingly.
+ panic!("Enums cannot have generic parameters");
+ }
+
+ let mut attrs = parent_attrs.clone();
+ attrs.add_attrs(&enm.attrs);
+ let variant_parent_attrs = attrs.attrs_for_inheritance(AttrInheritContext::Variant);
+
+ Enum {
+ name: (&enm.ident).into(),
+ docs: Docs::from_attrs(&enm.attrs),
+ variants: enm
+ .variants
+ .iter()
+ .map(|v| {
+ if !matches!(v.fields, syn::Fields::Unit) {
+ panic!("Enums cannot have fields, we only support C-like enums");
+ }
+ let new_discriminant = v
+ .discriminant
+ .as_ref()
+ .map(|d| {
+ // Reparsing, signed literals are represented
+ // as a negation expression
+ let lit: Result<syn::Lit, _> = syn::parse2(d.1.to_token_stream());
+ if let Ok(syn::Lit::Int(ref lit_int)) = lit {
+ lit_int.base10_parse::<isize>().unwrap()
+ } else {
+ panic!("Expected a discriminant to be a constant integer");
+ }
+ })
+ .unwrap_or_else(|| last_discriminant + 1);
+
+ last_discriminant = new_discriminant;
+ let mut v_attrs = variant_parent_attrs.clone();
+ v_attrs.add_attrs(&v.attrs);
+ (
+ (&v.ident).into(),
+ new_discriminant,
+ Docs::from_attrs(&v.attrs),
+ v_attrs,
+ )
+ })
+ .collect(),
+ methods: vec![],
+ attrs,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta::{self, Settings};
+
+ use syn;
+
+ use super::Enum;
+
+ #[test]
+ fn simple_enum() {
+ let mut settings = Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_yaml_snapshot!(Enum::new(
+ &syn::parse_quote! {
+ /// Some docs.
+ #[diplomat::rust_link(foo::Bar, Enum)]
+ enum MyLocalEnum {
+ Abc,
+ /// Some more docs.
+ Def
+ }
+ },
+ &Default::default()
+ ));
+ });
+ }
+
+ #[test]
+ fn enum_with_discr() {
+ let mut settings = Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_yaml_snapshot!(Enum::new(
+ &syn::parse_quote! {
+ /// Some docs.
+ #[diplomat::rust_link(foo::Bar, Enum)]
+ enum DiscriminantedEnum {
+ Abc = -1,
+ Def = 0,
+ Ghi = 1,
+ Jkl = 2,
+ }
+ },
+ &Default::default()
+ ));
+ });
+ }
+}
diff --git a/crates/diplomat_core/src/ast/idents.rs b/crates/diplomat_core/src/ast/idents.rs
new file mode 100644
index 0000000..2177923
--- /dev/null
+++ b/crates/diplomat_core/src/ast/idents.rs
@@ -0,0 +1,86 @@
+use proc_macro2::Span;
+use quote::{ToTokens, TokenStreamExt};
+use serde::{Deserialize, Serialize};
+use std::borrow::{Borrow, Cow};
+use std::fmt;
+
+/// An identifier, analogous to `syn::Ident` and `proc_macro2::Ident`.
+#[derive(Hash, Eq, PartialEq, Serialize, Clone, Debug, Ord, PartialOrd)]
+#[serde(transparent)]
+pub struct Ident(Cow<'static, str>);
+
+impl Ident {
+ /// Validate a string
+ fn validate(string: &str) -> syn::Result<()> {
+ syn::parse_str::<syn::Ident>(string).map(|_| {})
+ }
+
+ /// Attempt to create a new `Ident`.
+ ///
+ /// This function fails if the input isn't valid according to
+ /// `proc_macro2::Ident`'s invariants.
+ pub fn try_new(string: String) -> syn::Result<Self> {
+ Self::validate(&string).map(|_| Self(Cow::from(string)))
+ }
+
+ pub fn to_syn(&self) -> syn::Ident {
+ syn::Ident::new(self.as_str(), Span::call_site())
+ }
+
+ /// Get the `&str` representation.
+ pub fn as_str(&self) -> &str {
+ &self.0
+ }
+
+ /// An [`Ident`] containing "this".
+ pub const THIS: Self = Ident(Cow::Borrowed("this"));
+}
+
+impl From<&'static str> for Ident {
+ fn from(string: &'static str) -> Self {
+ Self::validate(string).unwrap();
+ Self(Cow::from(string))
+ }
+}
+
+impl From<String> for Ident {
+ fn from(string: String) -> Self {
+ Self::validate(&string).unwrap();
+ Self(Cow::from(string))
+ }
+}
+
+impl<'de> Deserialize<'de> for Ident {
+ /// The derived `Deserialize` allows for creating `Ident`s that do not uphold
+ /// the proper invariants. This custom impl ensures that this cannot happen.
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ Ok(Ident::from(String::deserialize(deserializer)?))
+ }
+}
+
+impl Borrow<str> for Ident {
+ fn borrow(&self) -> &str {
+ self.as_str()
+ }
+}
+
+impl fmt::Display for Ident {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.as_str().fmt(f)
+ }
+}
+
+impl From<&syn::Ident> for Ident {
+ fn from(ident: &syn::Ident) -> Self {
+ Self(Cow::from(ident.to_string()))
+ }
+}
+
+impl ToTokens for Ident {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ tokens.append(self.to_syn());
+ }
+}
diff --git a/crates/diplomat_core/src/ast/lifetimes.rs b/crates/diplomat_core/src/ast/lifetimes.rs
new file mode 100644
index 0000000..6e41392
--- /dev/null
+++ b/crates/diplomat_core/src/ast/lifetimes.rs
@@ -0,0 +1,609 @@
+use proc_macro2::Span;
+use quote::{quote, ToTokens};
+use serde::{Deserialize, Serialize};
+use std::fmt;
+
+use super::{Attrs, Docs, Ident, Param, SelfParam, TraitSelfParam, TypeName};
+
+/// A named lifetime, e.g. `'a`.
+///
+/// # Invariants
+///
+/// Cannot be `'static` or `'_`, use [`Lifetime`] to represent those instead.
+#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, PartialOrd, Ord)]
+#[serde(transparent)]
+pub struct NamedLifetime(Ident);
+
+impl NamedLifetime {
+ pub fn name(&self) -> &Ident {
+ &self.0
+ }
+}
+
+impl<'de> Deserialize<'de> for NamedLifetime {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ // Special `Deserialize` impl to ensure invariants.
+ let named = Ident::deserialize(deserializer)?;
+ if named.as_str() == "static" {
+ panic!("cannot be static");
+ }
+ Ok(NamedLifetime(named))
+ }
+}
+
+impl From<&syn::Lifetime> for NamedLifetime {
+ fn from(lt: &syn::Lifetime) -> Self {
+ Lifetime::from(lt).to_named().expect("cannot be static")
+ }
+}
+
+impl From<&NamedLifetime> for NamedLifetime {
+ fn from(this: &NamedLifetime) -> Self {
+ this.clone()
+ }
+}
+
+impl PartialEq<syn::Lifetime> for NamedLifetime {
+ fn eq(&self, other: &syn::Lifetime) -> bool {
+ other.ident == self.0.as_str()
+ }
+}
+
+impl fmt::Display for NamedLifetime {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "'{}", self.0)
+ }
+}
+
+impl ToTokens for NamedLifetime {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ use proc_macro2::{Punct, Spacing};
+ Punct::new('\'', Spacing::Joint).to_tokens(tokens);
+ self.0.to_tokens(tokens);
+ }
+}
+
+/// A lifetime dependency graph used for tracking which lifetimes outlive,
+/// and are outlived by, other lifetimes.
+///
+/// It is similar to [`syn::LifetimeDef`], except it can also track lifetime
+/// bounds defined in the `where` clause.
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub struct LifetimeEnv {
+ pub(crate) nodes: Vec<LifetimeNode>,
+}
+
+impl LifetimeEnv {
+ /// Construct an empty [`LifetimeEnv`].
+ ///
+ /// To create one outside of this module, use `LifetimeEnv::from_method_item`
+ /// or `LifetimeEnv::from` on `&syn::Generics`.
+ fn new() -> Self {
+ Self { nodes: vec![] }
+ }
+
+ /// Iterate through the names of the lifetimes in scope.
+ pub fn names(&self) -> impl Iterator<Item = &NamedLifetime> + Clone {
+ self.nodes.iter().map(|node| &node.lifetime)
+ }
+
+ /// Returns a [`LifetimeEnv`] for a method, accounting for lifetimes and bounds
+ /// defined in both the impl block and the method, as well as implicit lifetime
+ /// bounds in the optional `self` param, other param, and optional return type.
+ /// For example, the type `&'a Foo<'b>` implies `'b: 'a`.
+ pub fn from_method_item(
+ method: &syn::ImplItemFn,
+ impl_generics: Option<&syn::Generics>,
+ self_param: Option<&SelfParam>,
+ params: &[Param],
+ return_type: Option<&TypeName>,
+ ) -> Self {
+ let mut this = LifetimeEnv::new();
+ // The impl generics _must_ be loaded into the env first, since the method
+ // generics might use lifetimes defined in the impl, and `extend_generics`
+ // panics if `'a: 'b` where `'b` isn't declared by the time it finishes.
+ if let Some(generics) = impl_generics {
+ this.extend_generics(generics);
+ }
+ this.extend_generics(&method.sig.generics);
+
+ if let Some(self_param) = self_param {
+ this.extend_implicit_lifetime_bounds(&self_param.to_typename(), None);
+ }
+ for param in params {
+ this.extend_implicit_lifetime_bounds(¶m.ty, None);
+ }
+ if let Some(return_type) = return_type {
+ this.extend_implicit_lifetime_bounds(return_type, None);
+ }
+
+ this
+ }
+
+ pub fn from_trait_item(
+ trait_fct_item: &syn::TraitItem,
+ self_param: Option<&TraitSelfParam>,
+ params: &[Param],
+ return_type: Option<&TypeName>,
+ ) -> Self {
+ let mut this = LifetimeEnv::new();
+ if let syn::TraitItem::Fn(_) = trait_fct_item {
+ if let Some(self_param) = self_param {
+ this.extend_implicit_lifetime_bounds(&self_param.to_typename(), None);
+ }
+ for param in params {
+ this.extend_implicit_lifetime_bounds(¶m.ty, None);
+ }
+ if let Some(return_type) = return_type {
+ this.extend_implicit_lifetime_bounds(return_type, None);
+ }
+ } else {
+ panic!(
+ "Diplomat traits can only have associated methods and no other associated items."
+ )
+ }
+ this
+ }
+
+ pub fn from_trait(trt: &syn::ItemTrait) -> Self {
+ if trt.generics.lifetimes().next().is_some() {
+ panic!("Diplomat traits are not allowed to have any lifetime parameters")
+ }
+ LifetimeEnv::new()
+ }
+
+ /// Returns a [`LifetimeEnv`] for a struct, accounding for lifetimes and bounds
+ /// defined in the struct generics, as well as implicit lifetime bounds in
+ /// the struct's fields. For example, the field `&'a Foo<'b>` implies `'b: 'a`.
+ pub fn from_struct_item(
+ strct: &syn::ItemStruct,
+ fields: &[(Ident, TypeName, Docs, Attrs)],
+ ) -> Self {
+ let mut this = LifetimeEnv::new();
+ this.extend_generics(&strct.generics);
+ for (_, typ, _, _) in fields {
+ this.extend_implicit_lifetime_bounds(typ, None);
+ }
+ this
+ }
+
+ /// Traverse a type, adding any implicit lifetime bounds that arise from
+ /// having a reference to an opaque containing a lifetime.
+ /// For example, the type `&'a Foo<'b>` implies `'b: 'a`.
+ fn extend_implicit_lifetime_bounds(
+ &mut self,
+ typ: &TypeName,
+ behind_ref: Option<&NamedLifetime>,
+ ) {
+ match typ {
+ TypeName::Named(path_type) => {
+ if let Some(borrow_lifetime) = behind_ref {
+ let explicit_longer_than_borrow =
+ LifetimeTransitivity::longer_than(self, borrow_lifetime);
+ let mut implicit_longer_than_borrow = vec![];
+
+ for path_lifetime in path_type.lifetimes.iter() {
+ if let Lifetime::Named(path_lifetime) = path_lifetime {
+ if !explicit_longer_than_borrow.contains(&path_lifetime) {
+ implicit_longer_than_borrow.push(path_lifetime);
+ }
+ }
+ }
+
+ self.extend_bounds(
+ implicit_longer_than_borrow
+ .into_iter()
+ .map(|path_lifetime| (path_lifetime, Some(borrow_lifetime))),
+ );
+ }
+ }
+ TypeName::Reference(lifetime, _, typ) => {
+ let behind_ref = if let Lifetime::Named(named) = lifetime {
+ Some(named)
+ } else {
+ None
+ };
+ self.extend_implicit_lifetime_bounds(typ, behind_ref);
+ }
+ TypeName::Option(typ, _) => self.extend_implicit_lifetime_bounds(typ, None),
+ TypeName::Result(ok, err, _) => {
+ self.extend_implicit_lifetime_bounds(ok, None);
+ self.extend_implicit_lifetime_bounds(err, None);
+ }
+ _ => {}
+ }
+ }
+
+ /// Add the lifetimes from generic parameters and where bounds.
+ fn extend_generics(&mut self, generics: &syn::Generics) {
+ let generic_bounds = generics.params.iter().map(|generic| match generic {
+ syn::GenericParam::Type(_) => panic!("generic types are unsupported"),
+ syn::GenericParam::Lifetime(def) => (&def.lifetime, &def.bounds),
+ syn::GenericParam::Const(_) => panic!("const generics are unsupported"),
+ });
+
+ let generic_defs = generic_bounds.clone().map(|(lifetime, _)| lifetime);
+
+ self.extend_lifetimes(generic_defs);
+ self.extend_bounds(generic_bounds);
+
+ if let Some(ref where_clause) = generics.where_clause {
+ self.extend_bounds(where_clause.predicates.iter().map(|pred| match pred {
+ syn::WherePredicate::Type(_) => panic!("trait bounds are unsupported"),
+ syn::WherePredicate::Lifetime(pred) => (&pred.lifetime, &pred.bounds),
+ _ => panic!("Found unknown kind of where predicate"),
+ }));
+ }
+ }
+
+ /// Returns the number of lifetimes in the graph.
+ pub fn len(&self) -> usize {
+ self.nodes.len()
+ }
+
+ /// Returns `true` if the graph contains no lifetimes.
+ pub fn is_empty(&self) -> bool {
+ self.nodes.is_empty()
+ }
+
+ /// `<'a, 'b, 'c>`
+ ///
+ /// Write the existing lifetimes, excluding bounds, as generic parameters.
+ ///
+ /// To include lifetime bounds, use [`LifetimeEnv::lifetime_defs_to_tokens`].
+ pub fn lifetimes_to_tokens(&self) -> proc_macro2::TokenStream {
+ if self.is_empty() {
+ return quote! {};
+ }
+
+ let lifetimes = self.nodes.iter().map(|node| &node.lifetime);
+ quote! { <#(#lifetimes),*> }
+ }
+
+ /// Returns the index of a lifetime in the graph, or `None` if the lifetime
+ /// isn't in the graph.
+ pub(crate) fn id<L>(&self, lifetime: &L) -> Option<usize>
+ where
+ NamedLifetime: PartialEq<L>,
+ {
+ self.nodes
+ .iter()
+ .position(|node| &node.lifetime == lifetime)
+ }
+
+ /// Add isolated lifetimes to the graph.
+ fn extend_lifetimes<'a, L, I>(&mut self, iter: I)
+ where
+ NamedLifetime: PartialEq<L> + From<&'a L>,
+ L: 'a,
+ I: IntoIterator<Item = &'a L>,
+ {
+ for lifetime in iter {
+ if self.id(lifetime).is_some() {
+ panic!(
+ "lifetime name `{}` declared twice in the same scope",
+ NamedLifetime::from(lifetime)
+ );
+ }
+
+ self.nodes.push(LifetimeNode {
+ lifetime: lifetime.into(),
+ shorter: vec![],
+ longer: vec![],
+ });
+ }
+ }
+
+ /// Add edges to the lifetime graph.
+ ///
+ /// This method is decoupled from [`LifetimeEnv::extend_lifetimes`] because
+ /// generics can define new lifetimes, while `where` clauses cannot.
+ ///
+ /// # Panics
+ ///
+ /// This method panics if any of the lifetime bounds aren't already defined
+ /// in the graph. This isn't allowed by rustc in the first place, so it should
+ /// only ever occur when deserializing an invalid [`LifetimeEnv`].
+ fn extend_bounds<'a, L, B, I>(&mut self, iter: I)
+ where
+ NamedLifetime: PartialEq<L> + From<&'a L>,
+ L: 'a,
+ B: IntoIterator<Item = &'a L>,
+ I: IntoIterator<Item = (&'a L, B)>,
+ {
+ for (lifetime, bounds) in iter {
+ let long = self.id(lifetime).expect("use of undeclared lifetime, this is a bug: try calling `LifetimeEnv::extend_lifetimes` first");
+ for bound in bounds {
+ let short = self
+ .id(bound)
+ .expect("cannot use undeclared lifetime as a bound");
+ self.nodes[short].longer.push(long);
+ self.nodes[long].shorter.push(short);
+ }
+ }
+ }
+}
+
+impl fmt::Display for LifetimeEnv {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.to_token_stream().fmt(f)
+ }
+}
+
+impl ToTokens for LifetimeEnv {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ for node in self.nodes.iter() {
+ let lifetime = &node.lifetime;
+ if node.shorter.is_empty() {
+ tokens.extend(quote! { #lifetime, });
+ } else {
+ let bounds = node.shorter.iter().map(|&id| &self.nodes[id].lifetime);
+ tokens.extend(quote! { #lifetime: #(#bounds)+*, });
+ }
+ }
+ }
+}
+
+/// Serialize a [`LifetimeEnv`] as a map from lifetimes to their bounds.
+impl Serialize for LifetimeEnv {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::SerializeMap;
+ let mut seq = serializer.serialize_map(Some(self.len()))?;
+
+ for node in self.nodes.iter() {
+ /// Helper type for serializing bounds.
+ struct Bounds<'a> {
+ ids: &'a [usize],
+ nodes: &'a [LifetimeNode],
+ }
+
+ impl<'a> Serialize for Bounds<'a> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::SerializeSeq;
+ let mut seq = serializer.serialize_seq(Some(self.ids.len()))?;
+ for &id in self.ids {
+ seq.serialize_element(&self.nodes[id].lifetime)?;
+ }
+ seq.end()
+ }
+ }
+
+ seq.serialize_entry(
+ &node.lifetime,
+ &Bounds {
+ ids: &node.shorter[..],
+ nodes: &self.nodes,
+ },
+ )?;
+ }
+ seq.end()
+ }
+}
+
+impl<'de> Deserialize<'de> for LifetimeEnv {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ use std::collections::BTreeMap;
+
+ let m: BTreeMap<NamedLifetime, Vec<NamedLifetime>> =
+ Deserialize::deserialize(deserializer)?;
+
+ let mut this = LifetimeEnv::new();
+ this.extend_lifetimes(m.keys());
+ this.extend_bounds(m.iter());
+ Ok(this)
+ }
+}
+
+/// A lifetime, along with ptrs to all lifetimes that are explicitly
+/// shorter/longer than it.
+///
+/// This type is internal to [`LifetimeGraph`]- the ptrs are stored as `usize`s,
+/// meaning that they may be invalid if a `LifetimeEdges` is created in one
+/// `LifetimeGraph` and then used in another.
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub(crate) struct LifetimeNode {
+ /// The name of the lifetime.
+ pub(crate) lifetime: NamedLifetime,
+
+ /// Pointers to all lifetimes that this lives _at least_ as long as.
+ ///
+ /// Note: This doesn't account for transitivity.
+ pub(crate) shorter: Vec<usize>,
+
+ /// Pointers to all lifetimes that live _at least_ as long as this.
+ ///
+ /// Note: This doesn't account for transitivity.
+ pub(crate) longer: Vec<usize>,
+}
+
+/// A lifetime, analogous to [`syn::Lifetime`].
+#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
+#[non_exhaustive]
+pub enum Lifetime {
+ /// The `'static` lifetime.
+ Static,
+
+ /// A named lifetime, like `'a`.
+ Named(NamedLifetime),
+
+ /// An elided lifetime.
+ Anonymous,
+}
+
+impl Lifetime {
+ /// Returns the inner `NamedLifetime` if the lifetime is the `Named` variant,
+ /// otherwise `None`.
+ pub fn to_named(self) -> Option<NamedLifetime> {
+ if let Lifetime::Named(named) = self {
+ return Some(named);
+ }
+ None
+ }
+
+ /// Returns a reference to the inner `NamedLifetime` if the lifetime is the
+ /// `Named` variant, otherwise `None`.
+ pub fn as_named(&self) -> Option<&NamedLifetime> {
+ if let Lifetime::Named(named) = self {
+ return Some(named);
+ }
+ None
+ }
+}
+
+impl fmt::Display for Lifetime {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Lifetime::Static => "'static".fmt(f),
+ Lifetime::Named(ref named) => named.fmt(f),
+ Lifetime::Anonymous => "'_".fmt(f),
+ }
+ }
+}
+
+impl ToTokens for Lifetime {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ match self {
+ Lifetime::Static => syn::Lifetime::new("'static", Span::call_site()).to_tokens(tokens),
+ Lifetime::Named(ref s) => s.to_tokens(tokens),
+ Lifetime::Anonymous => syn::Lifetime::new("'_", Span::call_site()).to_tokens(tokens),
+ };
+ }
+}
+
+impl From<&syn::Lifetime> for Lifetime {
+ fn from(lt: &syn::Lifetime) -> Self {
+ if lt.ident == "static" {
+ Self::Static
+ } else {
+ Self::Named(NamedLifetime((<.ident).into()))
+ }
+ }
+}
+
+impl From<&Option<syn::Lifetime>> for Lifetime {
+ fn from(lt: &Option<syn::Lifetime>) -> Self {
+ lt.as_ref().map(Into::into).unwrap_or(Self::Anonymous)
+ }
+}
+
+impl Lifetime {
+ /// Converts the [`Lifetime`] back into an AST node that can be spliced into a program.
+ pub fn to_syn(&self) -> Option<syn::Lifetime> {
+ match *self {
+ Self::Static => Some(syn::Lifetime::new("'static", Span::call_site())),
+ Self::Anonymous => None,
+ Self::Named(ref s) => Some(syn::Lifetime::new(&s.to_string(), Span::call_site())),
+ }
+ }
+}
+
+/// Collect all lifetimes that are either longer_or_shorter
+pub struct LifetimeTransitivity<'env> {
+ env: &'env LifetimeEnv,
+ visited: Vec<bool>,
+ out: Vec<&'env NamedLifetime>,
+ longer_or_shorter: LongerOrShorter,
+}
+
+impl<'env> LifetimeTransitivity<'env> {
+ /// Returns a new [`LifetimeTransitivity`] that finds all longer lifetimes.
+ pub fn longer(env: &'env LifetimeEnv) -> Self {
+ Self::new(env, LongerOrShorter::Longer)
+ }
+
+ /// Returns a new [`LifetimeTransitivity`] that finds all shorter lifetimes.
+ pub fn shorter(env: &'env LifetimeEnv) -> Self {
+ Self::new(env, LongerOrShorter::Shorter)
+ }
+
+ /// Returns all the lifetimes longer than a provided `NamedLifetime`.
+ pub fn longer_than(env: &'env LifetimeEnv, named: &NamedLifetime) -> Vec<&'env NamedLifetime> {
+ let mut this = Self::new(env, LongerOrShorter::Longer);
+ this.visit(named);
+ this.finish()
+ }
+
+ /// Returns all the lifetimes shorter than the provided `NamedLifetime`.
+ pub fn shorter_than(env: &'env LifetimeEnv, named: &NamedLifetime) -> Vec<&'env NamedLifetime> {
+ let mut this = Self::new(env, LongerOrShorter::Shorter);
+ this.visit(named);
+ this.finish()
+ }
+
+ /// Returns a new [`LifetimeTransitivity`].
+ fn new(env: &'env LifetimeEnv, longer_or_shorter: LongerOrShorter) -> Self {
+ LifetimeTransitivity {
+ env,
+ visited: vec![false; env.len()],
+ out: vec![],
+ longer_or_shorter,
+ }
+ }
+
+ /// Visits a lifetime, as well as all the nodes it's transitively longer or
+ /// shorter than, depending on how the `LifetimeTransitivity` was constructed.
+ pub fn visit(&mut self, named: &NamedLifetime) {
+ if let Some(id) = self
+ .env
+ .nodes
+ .iter()
+ .position(|node| node.lifetime == *named)
+ {
+ self.dfs(id);
+ }
+ }
+
+ /// Performs depth-first search through the `LifetimeEnv` created at construction
+ /// for all nodes longer or shorter than the node at the provided index,
+ /// depending on how the `LifetimeTransitivity` was constructed.
+ fn dfs(&mut self, index: usize) {
+ // Note: all of these indexings SHOULD be valid because
+ // `visited.len() == nodes.len()`, and the ids come from
+ // calling `Iterator::position` on `nodes`, which never shrinks.
+ // So we should be able to change these to `get_unchecked`...
+ if !self.visited[index] {
+ self.visited[index] = true;
+
+ let node = &self.env.nodes[index];
+ self.out.push(&node.lifetime);
+ for &edge_index in self.longer_or_shorter.edges(node).iter() {
+ self.dfs(edge_index);
+ }
+ }
+ }
+
+ /// Returns the transitively reachable lifetimes.
+ pub fn finish(self) -> Vec<&'env NamedLifetime> {
+ self.out
+ }
+}
+
+/// A helper type for [`LifetimeTransitivity`] determining whether to find the
+/// transitively longer or transitively shorter lifetimes.
+enum LongerOrShorter {
+ Longer,
+ Shorter,
+}
+
+impl LongerOrShorter {
+ /// Returns either the indices of the longer or shorter lifetimes, depending
+ /// on `self`.
+ fn edges<'node>(&self, node: &'node LifetimeNode) -> &'node [usize] {
+ match self {
+ LongerOrShorter::Longer => &node.longer[..],
+ LongerOrShorter::Shorter => &node.shorter[..],
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/ast/methods.rs b/crates/diplomat_core/src/ast/methods.rs
new file mode 100644
index 0000000..dbf33a3
--- /dev/null
+++ b/crates/diplomat_core/src/ast/methods.rs
@@ -0,0 +1,615 @@
+use serde::{Deserialize, Serialize};
+use std::ops::ControlFlow;
+
+use super::docs::Docs;
+use super::{Attrs, Ident, Lifetime, LifetimeEnv, Mutability, PathType, TypeName};
+
+/// A method declared in the `impl` associated with an FFI struct.
+/// Includes both static and non-static methods, which can be distinguished
+/// by inspecting [`Method::self_param`].
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
+#[non_exhaustive]
+pub struct Method {
+ /// The name of the method as initially declared.
+ pub name: Ident,
+
+ /// Lines of documentation for the method.
+ pub docs: Docs,
+
+ /// The name of the generated `extern "C"` function
+ pub abi_name: Ident,
+
+ /// The `self` param of the method, if any.
+ pub self_param: Option<SelfParam>,
+
+ /// All non-`self` params taken by the method.
+ pub params: Vec<Param>,
+
+ /// The return type of the method, if any.
+ pub return_type: Option<TypeName>,
+
+ /// The lifetimes introduced in this method and surrounding impl block.
+ pub lifetime_env: LifetimeEnv,
+
+ /// The list of `cfg` attributes (if any).
+ ///
+ /// These are strings instead of `syn::Attribute` or `proc_macro2::TokenStream`
+ /// because those types are not `PartialEq`, `Hash`, `Serialize`, etc.
+ pub attrs: Attrs,
+}
+
+impl Method {
+ /// Extracts a [`Method`] from an AST node inside an `impl`.
+ pub fn from_syn(
+ m: &syn::ImplItemFn,
+ self_path_type: PathType,
+ impl_generics: Option<&syn::Generics>,
+ impl_attrs: &Attrs,
+ ) -> Method {
+ let mut attrs = impl_attrs.clone();
+ attrs.add_attrs(&m.attrs);
+
+ let self_ident = self_path_type.path.elements.last().unwrap();
+ let method_ident = &m.sig.ident;
+ let concat_method_ident = format!("{self_ident}_{method_ident}");
+ let extern_ident = syn::Ident::new(
+ &attrs.abi_rename.apply(concat_method_ident.into()),
+ m.sig.ident.span(),
+ );
+
+ let all_params = m
+ .sig
+ .inputs
+ .iter()
+ .filter_map(|a| match a {
+ syn::FnArg::Receiver(_) => None,
+ syn::FnArg::Typed(ref t) => Some(Param::from_syn(t, self_path_type.clone())),
+ })
+ .collect::<Vec<_>>();
+
+ let self_param = m
+ .sig
+ .receiver()
+ .map(|rec| SelfParam::from_syn(rec, self_path_type.clone()));
+
+ let return_ty = match &m.sig.output {
+ syn::ReturnType::Type(_, return_typ) => {
+ // When we allow lifetime elision, this is where we would want to
+ // support it so we can insert the expanded explicit lifetimes.
+ Some(TypeName::from_syn(
+ return_typ.as_ref(),
+ Some(self_path_type),
+ ))
+ }
+ syn::ReturnType::Default => None,
+ };
+
+ let lifetime_env = LifetimeEnv::from_method_item(
+ m,
+ impl_generics,
+ self_param.as_ref(),
+ &all_params[..],
+ return_ty.as_ref(),
+ );
+
+ Method {
+ name: Ident::from(method_ident),
+ docs: Docs::from_attrs(&m.attrs),
+ abi_name: Ident::from(&extern_ident),
+ self_param,
+ params: all_params,
+ return_type: return_ty,
+ lifetime_env,
+ attrs,
+ }
+ }
+
+ /// Returns the parameters that the output is lifetime-bound to.
+ ///
+ /// # Examples
+ ///
+ /// Given the following method:
+ /// ```ignore
+ /// fn foo<'a, 'b: 'a, 'c>(&'a self, bar: Bar<'b>, baz: Baz<'c>) -> FooBar<'a> { ... }
+ /// ```
+ /// Then this method would return the `&'a self` and `bar: Bar<'b>` params
+ /// because `'a` is in the return type, and `'b` must live at least as long
+ /// as `'a`. It wouldn't include `baz: Baz<'c>` though, because the return
+ /// type isn't bound by `'c` in any way.
+ ///
+ /// # Panics
+ ///
+ /// This method may panic if `TypeName::check_result_type_validity` (called by
+ /// `Method::check_validity`) doesn't pass first, since the result type may
+ /// contain elided lifetimes that we depend on for this method. The validity
+ /// checks ensure that the return type doesn't elide any lifetimes, ensuring
+ /// that this method will produce correct results.
+ pub fn borrowed_params(&self) -> BorrowedParams {
+ // To determine which params the return type is bound to, we just have to
+ // find the params that contain a lifetime that's also in the return type.
+ if let Some(ref return_type) = self.return_type {
+ // The lifetimes that must outlive the return type
+ let lifetimes = return_type.longer_lifetimes(&self.lifetime_env);
+
+ let held_self_param = self.self_param.as_ref().filter(|self_param| {
+ // Check if `self` is a reference with a lifetime in the return type.
+ if let Some((Lifetime::Named(ref name), _)) = self_param.reference {
+ if lifetimes.contains(&name) {
+ return true;
+ }
+ }
+ self_param.path_type.lifetimes.iter().any(|lt| {
+ if let Lifetime::Named(name) = lt {
+ lifetimes.contains(&name)
+ } else {
+ false
+ }
+ })
+ });
+
+ // Collect all the params that contain a named lifetime that's also
+ // in the return type.
+ let held_params = self
+ .params
+ .iter()
+ .filter_map(|param| {
+ let mut lt_kind = LifetimeKind::ReturnValue;
+ param
+ .ty
+ .visit_lifetimes(&mut |lt, _| {
+ // Thanks to `TypeName::visit_lifetimes`, we can
+ // traverse the lifetimes without allocations and
+ // short-circuit if we find a match.
+ match lt {
+ Lifetime::Named(name) if lifetimes.contains(&name) => {
+ return ControlFlow::Break(());
+ }
+ Lifetime::Static => {
+ lt_kind = LifetimeKind::Static;
+ return ControlFlow::Break(());
+ }
+ _ => {}
+ };
+ ControlFlow::Continue(())
+ })
+ .is_break()
+ .then(|| (param, lt_kind))
+ })
+ .collect();
+
+ BorrowedParams(held_self_param, held_params)
+ } else {
+ BorrowedParams(None, vec![])
+ }
+ }
+
+ /// Checks whether the method qualifies for special write handling.
+ /// To qualify, a method must:
+ /// - not return any value
+ /// - have the last argument be an `&mut diplomat_runtime::DiplomatWrite`
+ ///
+ /// Typically, methods of this form will be transformed in the bindings to a
+ /// method that doesn't take the write as an argument but instead creates
+ /// one locally and just returns the final string.
+ pub fn is_write_out(&self) -> bool {
+ let return_compatible = self
+ .return_type
+ .as_ref()
+ .map(|return_type| match return_type {
+ TypeName::Unit => true,
+ TypeName::Result(ok, _, _) | TypeName::Option(ok, _) => {
+ matches!(ok.as_ref(), TypeName::Unit)
+ }
+
+ _ => false,
+ })
+ .unwrap_or(true);
+
+ return_compatible && self.params.last().map(Param::is_write).unwrap_or(false)
+ }
+
+ /// Checks if any parameters are write (regardless of other compatibilities for write output)
+ pub fn has_write_param(&self) -> bool {
+ self.params.iter().any(|p| p.is_write())
+ }
+
+ /// Returns the documentation block
+ pub fn docs(&self) -> &Docs {
+ &self.docs
+ }
+}
+
+/// The `self` parameter taken by a [`Method`].
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
+#[non_exhaustive]
+pub struct SelfParam {
+ /// The lifetime and mutability of the `self` param, if it's a reference.
+ pub reference: Option<(Lifetime, Mutability)>,
+
+ /// The type of the parameter, which will be a named reference to
+ /// the associated struct,
+ pub path_type: PathType,
+
+ /// Associated attributes with this self parameter. Used in Demo Generation, mostly.
+ pub attrs: Attrs,
+}
+
+impl SelfParam {
+ pub fn to_typename(&self) -> TypeName {
+ let typ = TypeName::Named(self.path_type.clone());
+ if let Some((ref lifetime, ref mutability)) = self.reference {
+ return TypeName::Reference(lifetime.clone(), *mutability, Box::new(typ));
+ }
+ typ
+ }
+
+ pub fn from_syn(rec: &syn::Receiver, path_type: PathType) -> Self {
+ SelfParam {
+ reference: rec
+ .reference
+ .as_ref()
+ .map(|(_, lt)| (lt.into(), Mutability::from_syn(&rec.mutability))),
+ path_type,
+ attrs: Attrs::from_attrs(&rec.attrs),
+ }
+ }
+}
+
+/// The `self` parameter taken by a [`TraitMethod`].
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug, Deserialize)]
+#[non_exhaustive]
+pub struct TraitSelfParam {
+ /// The lifetime and mutability of the `self` param, if it's a reference.
+ pub reference: Option<(Lifetime, Mutability)>,
+
+ /// The trait of the parameter, which will be a named reference to
+ /// the associated trait,
+ pub path_trait: PathType,
+}
+
+impl TraitSelfParam {
+ pub fn to_typename(&self) -> TypeName {
+ let typ = TypeName::ImplTrait(self.path_trait.clone());
+ if let Some((ref lifetime, ref mutability)) = self.reference {
+ return TypeName::Reference(lifetime.clone(), *mutability, Box::new(typ));
+ }
+ typ
+ }
+
+ pub fn from_syn(rec: &syn::Receiver, path_trait: PathType) -> Self {
+ TraitSelfParam {
+ reference: rec
+ .reference
+ .as_ref()
+ .map(|(_, lt)| (lt.into(), Mutability::from_syn(&rec.mutability))),
+ path_trait,
+ }
+ }
+}
+
+/// A parameter taken by a [`Method`], not including `self`.
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
+#[non_exhaustive]
+pub struct Param {
+ /// The name of the parameter in the original method declaration.
+ pub name: Ident,
+
+ /// The type of the parameter.
+ pub ty: TypeName,
+
+ /// Parameter attributes (like #[diplomat::demo(label = "Out")])
+ pub attrs: Attrs,
+}
+
+impl Param {
+ /// Check if this parameter is a Write
+ pub fn is_write(&self) -> bool {
+ match self.ty {
+ TypeName::Reference(_, Mutability::Mutable, ref w) => **w == TypeName::Write,
+ _ => false,
+ }
+ }
+
+ pub fn from_syn(t: &syn::PatType, self_path_type: PathType) -> Self {
+ let ident = match t.pat.as_ref() {
+ syn::Pat::Ident(ident) => ident,
+ _ => panic!("Unexpected param type"),
+ };
+
+ let attrs = Attrs::from_attrs(&t.attrs);
+
+ Param {
+ name: (&ident.ident).into(),
+ ty: TypeName::from_syn(&t.ty, Some(self_path_type)),
+ attrs,
+ }
+ }
+}
+
+/// The type of lifetime.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum LifetimeKind {
+ /// Param must live at least as long as the returned object.
+ ReturnValue,
+ /// Param must live for the duration of the program.
+ Static,
+}
+
+#[derive(Default, Debug)]
+/// Parameters in a method that might be borrowed in the return type.
+#[non_exhaustive]
+pub struct BorrowedParams<'a>(
+ pub Option<&'a SelfParam>,
+ pub Vec<(&'a Param, LifetimeKind)>,
+);
+
+impl BorrowedParams<'_> {
+ /// Returns an [`Iterator`] through the names of the parameters that are borrowed
+ /// for the lifetime of the return value, accepting an `Ident` that the `self`
+ /// param will be called if present.
+ pub fn return_names<'a>(&'a self, self_name: &'a Ident) -> impl Iterator<Item = &'a Ident> {
+ self.0.iter().map(move |_| self_name).chain(
+ self.1
+ .iter()
+ .filter(|(_, ltk)| (*ltk == LifetimeKind::ReturnValue))
+ .map(|(param, _)| ¶m.name),
+ )
+ }
+
+ /// Returns an [`Iterator`] through the names of the parameters that are borrowed for a
+ /// static lifetime.
+ pub fn static_names(&self) -> impl Iterator<Item = &'_ Ident> {
+ self.1
+ .iter()
+ .filter(|(_, ltk)| (*ltk == LifetimeKind::Static))
+ .map(|(param, _)| ¶m.name)
+ }
+
+ /// Returns `true` if a provided param name is included in the borrowed params,
+ /// otherwise `false`.
+ ///
+ /// This method doesn't check the `self` parameter. Use
+ /// [`BorrowedParams::borrows_self`] instead.
+ pub fn contains(&self, param_name: &Ident) -> bool {
+ self.1.iter().any(|(param, _)| ¶m.name == param_name)
+ }
+
+ /// Returns `true` if there are no borrowed parameters, otherwise `false`.
+ pub fn is_empty(&self) -> bool {
+ self.0.is_none() && self.1.is_empty()
+ }
+
+ /// Returns `true` if the `self` param is borrowed, otherwise `false`.
+ pub fn borrows_self(&self) -> bool {
+ self.0.is_some()
+ }
+
+ /// Returns `true` if there are any borrowed params, otherwise `false`.
+ pub fn borrows_params(&self) -> bool {
+ !self.1.is_empty()
+ }
+
+ /// Returns the number of borrowed params.
+ pub fn len(&self) -> usize {
+ self.1.len() + usize::from(self.0.is_some())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta;
+
+ use syn;
+
+ use crate::ast::{Attrs, Ident, Method, Path, PathType};
+
+ #[test]
+ fn static_methods() {
+ insta::assert_yaml_snapshot!(Method::from_syn(
+ &syn::parse_quote! {
+ /// Some docs.
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo(x: u64, y: MyCustomStruct) {
+
+ }
+ },
+ PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
+ None,
+ &Attrs::default()
+ ));
+
+ insta::assert_yaml_snapshot!(Method::from_syn(
+ &syn::parse_quote! {
+ /// Some docs.
+ /// Some more docs.
+ ///
+ /// Even more docs.
+ #[diplomat::rust_link(foo::Bar::batz, FnInEnum)]
+ fn foo(x: u64, y: MyCustomStruct) -> u64 {
+ x
+ }
+ },
+ PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
+ None,
+ &Attrs::default()
+ ));
+ }
+
+ #[test]
+ fn cfged_method() {
+ insta::assert_yaml_snapshot!(Method::from_syn(
+ &syn::parse_quote! {
+ /// Some docs.
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ #[cfg(any(feature = "foo", not(feature = "bar")))]
+ fn foo(x: u64, y: MyCustomStruct) {
+
+ }
+ },
+ PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
+ None,
+ &Attrs::default()
+ ));
+ }
+
+ #[test]
+ fn nonstatic_methods() {
+ insta::assert_yaml_snapshot!(Method::from_syn(
+ &syn::parse_quote! {
+ fn foo(&self, x: u64, y: MyCustomStruct) {
+
+ }
+ },
+ PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
+ None,
+ &Attrs::default()
+ ));
+
+ insta::assert_yaml_snapshot!(Method::from_syn(
+ &syn::parse_quote! {
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo(&mut self, x: u64, y: MyCustomStruct) -> u64 {
+ x
+ }
+ },
+ PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
+ None,
+ &Attrs::default()
+ ));
+ }
+
+ macro_rules! assert_borrowed_params {
+ ([$($return_param:ident),*] $(, [$($static_param:ident),*])? => $($tokens:tt)* ) => {{
+ let method = Method::from_syn(
+ &syn::parse_quote! { $($tokens)* },
+ PathType::new(Path::empty().sub_path(Ident::from("MyStructContainingMethod"))),
+ None,
+ &Attrs::default()
+ );
+
+ let borrowed_params = method.borrowed_params();
+ // The ident parser in syn doesn't allow `self`, so we use "this" as a placeholder
+ // and then change it.
+ let mut actual_return: Vec<&str> = borrowed_params.return_names(&Ident::THIS).map(|ident| ident.as_str()).collect();
+ if borrowed_params.0.is_some() {
+ actual_return[0] = "self";
+ }
+ let expected_return: &[&str] = &[$(stringify!($return_param)),*];
+ assert_eq!(actual_return, expected_return);
+ let actual_static: Vec<&str> = borrowed_params.static_names().map(|ident| ident.as_str()).collect();
+ let expected_static: &[&str] = &[$($(stringify!($static_param)),*)?];
+ assert_eq!(actual_static, expected_static);
+ }};
+ }
+
+ #[test]
+ fn static_params_held_by_return_type() {
+ assert_borrowed_params! { [first, second] =>
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo<'a, 'b>(first: &'a First, second: &'b Second, third: &Third) -> Foo<'a, 'b> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [hold] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn transitivity<'a, 'b: 'a, 'c: 'b, 'd: 'c, 'e: 'd, 'x>(hold: &'x One<'e>, nohold: &One<'x>) -> Box<Foo<'a>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [hold] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn a_le_b_and_b_le_a<'a: 'b, 'b: 'a>(hold: &'b Bar, nohold: &'c Bar) -> Box<Foo<'a>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [a, b, c, d] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn many_dependents<'a, 'b: 'a, 'c: 'a, 'd: 'b, 'x, 'y>(a: &'x One<'a>, b: &'b One<'a>, c: &Two<'x, 'c>, d: &'x Two<'d, 'y>, nohold: &'x Two<'x, 'y>) -> Box<Foo<'a>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [hold] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn return_outlives_param<'short, 'long: 'short>(hold: &Two<'long, 'short>, nohold: &'short One<'short>) -> Box<Foo<'long>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [hold] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn transitivity_deep_types<'a, 'b: 'a, 'c: 'b, 'd: 'c>(hold: Option<Box<Bar<'d>>>, nohold: &'a Box<Option<Baz<'a>>>) -> Result<Box<Foo<'b>>, Error> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [top, left, right, bottom] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn diamond_top<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'top>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [left, bottom] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn diamond_left<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'left>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [right, bottom] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn diamond_right<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'right>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [bottom] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn diamond_bottom<'top, 'left: 'top, 'right: 'top, 'bottom: 'left + 'right>(top: One<'top>, left: One<'left>, right: One<'right>, bottom: One<'bottom>) -> Box<Foo<'bottom>> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [a, b, c, d] =>
+ #[diplomat::rust_link(Foo, FnInStruct)]
+ fn diamond_and_nested_types<'a, 'b: 'a, 'c: 'b, 'd: 'b + 'c, 'x, 'y>(a: &'x One<'a>, b: &'y One<'b>, c: &One<'c>, d: &One<'d>, nohold: &One<'x>) -> Box<Foo<'a>> {
+ unimplemented!()
+ }
+ }
+ }
+
+ #[test]
+ fn nonstatic_params_held_by_return_type() {
+ assert_borrowed_params! { [self] =>
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo<'a>(&'a self) -> Foo<'a> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [self, foo, bar] =>
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo<'x, 'y>(&'x self, foo: &'x Foo, bar: &Bar<'y>, baz: &Baz) -> Foo<'x, 'y> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [self, bar] =>
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo<'a, 'b>(&'a self, bar: Bar<'b>) -> Foo<'a, 'b> {
+ unimplemented!()
+ }
+ }
+
+ assert_borrowed_params! { [self, bar], [baz] =>
+ #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]
+ fn foo<'a, 'b>(&'a self, bar: Bar<'b>, baz: &'static str) -> Foo<'a, 'b, 'static> {
+ unimplemented!()
+ }
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/ast/mod.rs b/crates/diplomat_core/src/ast/mod.rs
new file mode 100644
index 0000000..bde46af
--- /dev/null
+++ b/crates/diplomat_core/src/ast/mod.rs
@@ -0,0 +1,40 @@
+/// As part of the macro expansion and code generation process, Diplomat
+/// generates a simplified version of the Rust AST that captures special
+/// types such as opaque structs, [`Box`], and [`Result`] with utilities
+/// for handling such types.
+pub mod attrs;
+pub(crate) use attrs::AttrInheritContext;
+pub use attrs::Attrs;
+
+mod methods;
+pub use methods::{BorrowedParams, Method, Param, SelfParam, TraitSelfParam};
+
+mod modules;
+pub use modules::{File, Module};
+
+mod structs;
+pub use structs::{OpaqueStruct, Struct};
+
+mod traits;
+pub use traits::{Trait, TraitMethod};
+
+mod enums;
+pub use enums::Enum;
+
+mod types;
+pub use types::{
+ CustomType, LifetimeOrigin, ModSymbol, Mutability, PathType, PrimitiveType, StdlibOrDiplomat,
+ StringEncoding, TypeName,
+};
+
+pub(crate) mod lifetimes;
+pub use lifetimes::{Lifetime, LifetimeEnv, LifetimeTransitivity, NamedLifetime};
+
+mod paths;
+pub use paths::Path;
+
+mod idents;
+pub use idents::Ident;
+
+mod docs;
+pub use docs::{DocType, Docs, DocsUrlGenerator, RustLink, RustLinkDisplay};
diff --git a/crates/diplomat_core/src/ast/modules.rs b/crates/diplomat_core/src/ast/modules.rs
new file mode 100644
index 0000000..0879929
--- /dev/null
+++ b/crates/diplomat_core/src/ast/modules.rs
@@ -0,0 +1,416 @@
+use std::collections::{BTreeMap, HashSet};
+use std::fmt::Write as _;
+
+use quote::ToTokens;
+use serde::Serialize;
+use syn::{ImplItem, Item, ItemMod, UseTree, Visibility};
+
+use super::{
+ AttrInheritContext, Attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability,
+ OpaqueStruct, Path, PathType, RustLink, Struct, Trait,
+};
+use crate::environment::*;
+
+/// Custom Diplomat attribute that can be placed on a struct definition.
+#[derive(Debug)]
+enum DiplomatStructAttribute {
+ /// The `#[diplomat::out]` attribute, used for non-opaque structs that
+ /// contain an owned opaque in the form of a `Box`.
+ Out,
+ /// The `#[diplomat::opaque]` attribute, used for marking a struct as opaque.
+ /// Note that opaque structs can be borrowed in return types, but cannot
+ /// be passed into a function behind a mutable reference.
+ Opaque,
+ /// The `#[diplomat::opaque_mut]` attribute, used for marking a struct as
+ /// opaque and mutable.
+ /// Note that mutable opaque structs can never be borrowed in return types
+ /// (even immutably!), but can be passed into a function behind a mutable
+ /// reference.
+ OpaqueMut,
+}
+
+impl DiplomatStructAttribute {
+ /// Parses a [`DiplomatStructAttribute`] from an array of [`syn::Attribute`]s.
+ /// If more than one kind is found, an error is returned containing all the
+ /// ones encountered, since all the current attributes are disjoint.
+ fn parse(attrs: &[syn::Attribute]) -> Result<Option<Self>, Vec<Self>> {
+ let mut buf = String::with_capacity(32);
+ let mut res = Ok(None);
+ for attr in attrs {
+ buf.clear();
+ write!(&mut buf, "{}", attr.path().to_token_stream()).unwrap();
+ let parsed = match buf.as_str() {
+ "diplomat :: out" => Some(Self::Out),
+ "diplomat :: opaque" => Some(Self::Opaque),
+ "diplomat :: opaque_mut" => Some(Self::OpaqueMut),
+ _ => None,
+ };
+
+ if let Some(parsed) = parsed {
+ match res {
+ Ok(None) => res = Ok(Some(parsed)),
+ Ok(Some(first)) => res = Err(vec![first, parsed]),
+ Err(ref mut errors) => errors.push(parsed),
+ }
+ }
+ }
+
+ res
+ }
+}
+
+#[derive(Clone, Serialize, Debug)]
+#[non_exhaustive]
+pub struct Module {
+ pub name: Ident,
+ pub imports: Vec<(Path, Ident)>,
+ pub declared_types: BTreeMap<Ident, CustomType>,
+ pub declared_traits: BTreeMap<Ident, Trait>,
+ pub sub_modules: Vec<Module>,
+ pub attrs: Attrs,
+}
+
+impl Module {
+ pub fn all_rust_links(&self) -> HashSet<&RustLink> {
+ let mut rust_links = self
+ .declared_types
+ .values()
+ .flat_map(|t| t.all_rust_links())
+ .collect::<HashSet<_>>();
+
+ self.sub_modules.iter().for_each(|m| {
+ rust_links.extend(m.all_rust_links().iter());
+ });
+ rust_links
+ }
+
+ pub fn insert_all_types(&self, in_path: Path, out: &mut Env) {
+ let mut mod_symbols = ModuleEnv::new(self.attrs.clone());
+
+ self.imports.iter().for_each(|(path, name)| {
+ mod_symbols.insert(name.clone(), ModSymbol::Alias(path.clone()));
+ });
+
+ self.declared_types.iter().for_each(|(k, v)| {
+ if mod_symbols
+ .insert(k.clone(), ModSymbol::CustomType(v.clone()))
+ .is_some()
+ {
+ panic!("Two types were declared with the same name, this needs to be implemented");
+ }
+ });
+
+ self.declared_traits.iter().for_each(|(k, v)| {
+ if mod_symbols
+ .insert(k.clone(), ModSymbol::Trait(v.clone()))
+ .is_some()
+ {
+ panic!("Two traits were declared with the same name, this needs to be implemented");
+ }
+ });
+
+ let path_to_self = in_path.sub_path(self.name.clone());
+ self.sub_modules.iter().for_each(|m| {
+ m.insert_all_types(path_to_self.clone(), out);
+ mod_symbols.insert(m.name.clone(), ModSymbol::SubModule(m.name.clone()));
+ });
+
+ out.insert(path_to_self, mod_symbols);
+ }
+
+ pub fn from_syn(input: &ItemMod, force_analyze: bool) -> Module {
+ let mut custom_types_by_name = BTreeMap::new();
+ let mut custom_traits_by_name = BTreeMap::new();
+ let mut sub_modules = Vec::new();
+ let mut imports = Vec::new();
+
+ let analyze_types = force_analyze
+ || input
+ .attrs
+ .iter()
+ .any(|a| a.path().to_token_stream().to_string() == "diplomat :: bridge");
+
+ let mod_attrs: Attrs = (&*input.attrs).into();
+
+ let impl_parent_attrs: Attrs =
+ mod_attrs.attrs_for_inheritance(AttrInheritContext::MethodOrImplFromModule);
+ let type_parent_attrs: Attrs = mod_attrs.attrs_for_inheritance(AttrInheritContext::Type);
+
+ input
+ .content
+ .as_ref()
+ .map(|t| &t.1[..])
+ .unwrap_or_default()
+ .iter()
+ .for_each(|a| match a {
+ Item::Use(u) => {
+ if analyze_types {
+ extract_imports(&Path::empty(), &u.tree, &mut imports);
+ }
+ }
+ Item::Struct(strct) => {
+ if analyze_types {
+ let custom_type = match DiplomatStructAttribute::parse(&strct.attrs[..]) {
+ Ok(None) => CustomType::Struct(Struct::new(strct, false, &type_parent_attrs)),
+ Ok(Some(DiplomatStructAttribute::Out)) => {
+ CustomType::Struct(Struct::new(strct, true, &type_parent_attrs))
+ }
+ Ok(Some(DiplomatStructAttribute::Opaque)) => {
+ CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Immutable, &type_parent_attrs))
+ }
+ Ok(Some(DiplomatStructAttribute::OpaqueMut)) => {
+ CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Mutable, &type_parent_attrs))
+ }
+ Err(errors) => {
+ panic!("Multiple conflicting Diplomat struct attributes, there can be at most one: {errors:?}");
+ }
+ };
+
+ custom_types_by_name.insert(Ident::from(&strct.ident), custom_type);
+ }
+ }
+
+ Item::Enum(enm) => {
+ if analyze_types {
+ let ident = (&enm.ident).into();
+ let enm = Enum::new(enm, &type_parent_attrs);
+ custom_types_by_name
+ .insert(ident, CustomType::Enum(enm));
+ }
+ }
+
+ Item::Impl(imp) => {
+ if analyze_types && imp.trait_.is_none() {
+ let self_path = match imp.self_ty.as_ref() {
+ syn::Type::Path(s) => PathType::from(s),
+ _ => panic!("Self type not found"),
+ };
+ let mut impl_attrs = impl_parent_attrs.clone();
+ impl_attrs.add_attrs(&imp.attrs);
+ let method_parent_attrs = impl_attrs.attrs_for_inheritance(AttrInheritContext::MethodFromImpl);
+ let mut new_methods = imp
+ .items
+ .iter()
+ .filter_map(|i| match i {
+ ImplItem::Fn(m) => Some(m),
+ _ => None,
+ })
+ .filter(|m| matches!(m.vis, Visibility::Public(_)))
+ .map(|m| Method::from_syn(m, self_path.clone(), Some(&imp.generics), &method_parent_attrs))
+ .collect();
+
+ let self_ident = self_path.path.elements.last().unwrap();
+
+ match custom_types_by_name.get_mut(self_ident).unwrap() {
+ CustomType::Struct(strct) => {
+ strct.methods.append(&mut new_methods);
+ }
+ CustomType::Opaque(strct) => {
+ strct.methods.append(&mut new_methods);
+ }
+ CustomType::Enum(enm) => {
+ enm.methods.append(&mut new_methods);
+ }
+ }
+ }
+ }
+ Item::Mod(item_mod) => {
+ sub_modules.push(Module::from_syn(item_mod, false));
+ }
+ Item::Trait(trt) => {
+ if analyze_types {
+ let ident = (&trt.ident).into();
+ let trt = Trait::new(trt, &type_parent_attrs);
+ custom_traits_by_name
+ .insert(ident, trt);
+ }
+ }
+ _ => {}
+ });
+
+ Module {
+ name: (&input.ident).into(),
+ imports,
+ declared_types: custom_types_by_name,
+ declared_traits: custom_traits_by_name,
+ sub_modules,
+ attrs: mod_attrs,
+ }
+ }
+}
+
+fn extract_imports(base_path: &Path, use_tree: &UseTree, out: &mut Vec<(Path, Ident)>) {
+ match use_tree {
+ UseTree::Name(name) => out.push((
+ base_path.sub_path((&name.ident).into()),
+ (&name.ident).into(),
+ )),
+ UseTree::Path(path) => {
+ extract_imports(&base_path.sub_path((&path.ident).into()), &path.tree, out)
+ }
+ UseTree::Glob(_) => todo!("Glob imports are not yet supported"),
+ UseTree::Group(group) => {
+ group
+ .items
+ .iter()
+ .for_each(|i| extract_imports(base_path, i, out));
+ }
+ UseTree::Rename(rename) => out.push((
+ base_path.sub_path((&rename.ident).into()),
+ (&rename.rename).into(),
+ )),
+ }
+}
+
+#[derive(Serialize, Clone, Debug)]
+#[non_exhaustive]
+pub struct File {
+ pub modules: BTreeMap<String, Module>,
+}
+
+impl File {
+ /// Fuses all declared types into a single environment `HashMap`.
+ pub fn all_types(&self) -> Env {
+ let mut out = Env::default();
+ let mut top_symbols = ModuleEnv::new(Default::default());
+
+ self.modules.values().for_each(|m| {
+ m.insert_all_types(Path::empty(), &mut out);
+ top_symbols.insert(m.name.clone(), ModSymbol::SubModule(m.name.clone()));
+ });
+
+ out.insert(Path::empty(), top_symbols);
+
+ out
+ }
+
+ pub fn all_rust_links(&self) -> HashSet<&RustLink> {
+ self.modules
+ .values()
+ .flat_map(|m| m.all_rust_links().into_iter())
+ .collect()
+ }
+}
+
+impl From<&syn::File> for File {
+ /// Get all custom types across all modules defined in a given file.
+ fn from(file: &syn::File) -> File {
+ let mut out = BTreeMap::new();
+ file.items.iter().for_each(|i| {
+ if let Item::Mod(item_mod) = i {
+ out.insert(
+ item_mod.ident.to_string(),
+ Module::from_syn(item_mod, false),
+ );
+ }
+ });
+
+ File { modules: out }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta::{self, Settings};
+
+ use syn;
+
+ use crate::ast::{File, Module};
+
+ #[test]
+ fn simple_mod() {
+ let mut settings = Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_yaml_snapshot!(Module::from_syn(
+ &syn::parse_quote! {
+ mod ffi {
+ struct NonOpaqueStruct {
+ a: i32,
+ b: Box<NonOpaqueStruct>
+ }
+
+ impl NonOpaqueStruct {
+ pub fn new(x: i32) -> NonOpaqueStruct {
+ unimplemented!();
+ }
+
+ pub fn set_a(&mut self, new_a: i32) {
+ self.a = new_a;
+ }
+ }
+
+ #[diplomat::opaque]
+ struct OpaqueStruct {
+ a: SomeExternalType
+ }
+
+ impl OpaqueStruct {
+ pub fn new() -> Box<OpaqueStruct> {
+ unimplemented!();
+ }
+
+ pub fn get_string(&self) -> String {
+ unimplemented!()
+ }
+ }
+ }
+ },
+ true
+ ));
+ });
+ }
+
+ #[test]
+ fn method_visibility() {
+ let mut settings = Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_yaml_snapshot!(Module::from_syn(
+ &syn::parse_quote! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct Foo {}
+
+ impl Foo {
+ pub fn pub_fn() {
+ unimplemented!()
+ }
+ pub(crate) fn pub_crate_fn() {
+ unimplemented!()
+ }
+ pub(super) fn pub_super_fn() {
+ unimplemented!()
+ }
+ fn priv_fn() {
+ unimplemented!()
+ }
+ }
+ }
+ },
+ true
+ ));
+ });
+ }
+
+ #[test]
+ fn import_in_non_diplomat_not_analyzed() {
+ let mut settings = Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_yaml_snapshot!(File::from(&syn::parse_quote! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct Foo {}
+ }
+
+ mod other {
+ use something::*;
+ }
+ }));
+ });
+ }
+}
diff --git a/crates/diplomat_core/src/ast/paths.rs b/crates/diplomat_core/src/ast/paths.rs
new file mode 100644
index 0000000..8bd9129
--- /dev/null
+++ b/crates/diplomat_core/src/ast/paths.rs
@@ -0,0 +1,77 @@
+use serde::{Deserialize, Serialize};
+use std::fmt;
+
+use super::Ident;
+
+#[derive(Hash, Eq, PartialEq, Deserialize, Serialize, Clone, Debug, Ord, PartialOrd)]
+#[non_exhaustive]
+pub struct Path {
+ pub elements: Vec<Ident>,
+}
+
+impl Path {
+ pub fn get_super(&self) -> Path {
+ let mut new_elements = self.elements.clone();
+ new_elements.remove(new_elements.len() - 1);
+ Path {
+ elements: new_elements,
+ }
+ }
+
+ pub fn sub_path(&self, ident: Ident) -> Path {
+ let mut new_elements = self.elements.clone();
+ new_elements.push(ident);
+ Path {
+ elements: new_elements,
+ }
+ }
+
+ pub fn to_syn(&self) -> syn::Path {
+ syn::Path {
+ leading_colon: None,
+ segments: self
+ .elements
+ .iter()
+ .map(|s| syn::PathSegment {
+ ident: s.to_syn(),
+ arguments: syn::PathArguments::None,
+ })
+ .collect(),
+ }
+ }
+
+ pub fn from_syn(path: &syn::Path) -> Path {
+ Path {
+ elements: path
+ .segments
+ .iter()
+ .map(|seg| (&seg.ident).into())
+ .collect(),
+ }
+ }
+
+ pub fn empty() -> Path {
+ Path { elements: vec![] }
+ }
+}
+
+impl fmt::Display for Path {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some((head, tail)) = self.elements.split_first() {
+ head.fmt(f)?;
+ for seg in tail {
+ "::".fmt(f)?;
+ seg.fmt(f)?;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl FromIterator<Ident> for Path {
+ fn from_iter<T: IntoIterator<Item = Ident>>(iter: T) -> Self {
+ Path {
+ elements: iter.into_iter().collect(),
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__attr.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__attr.snap
new file mode 100644
index 0000000..d105a73
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__attr.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr
+---
+cfg:
+ Any:
+ - BackendName: cpp
+ - NameValue:
+ - has
+ - overloading
+meta: namespacing
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-2.snap
new file mode 100644
index 0000000..cc58dea
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-2.snap
@@ -0,0 +1,6 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr_cfg
+---
+BackendName: cpp
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-3.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-3.snap
new file mode 100644
index 0000000..635fcc9
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-3.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr_cfg
+---
+NameValue:
+ - has
+ - overloading
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-4.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-4.snap
new file mode 100644
index 0000000..635fcc9
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-4.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr_cfg
+---
+NameValue:
+ - has
+ - overloading
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-5.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-5.snap
new file mode 100644
index 0000000..5f7daa1
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs-5.snap
@@ -0,0 +1,14 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr_cfg
+---
+Any:
+ - All:
+ - Star
+ - BackendName: cpp
+ - NameValue:
+ - has
+ - overloading
+ - Not:
+ BackendName: js
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs.snap
new file mode 100644
index 0000000..658b25d
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__cfgs.snap
@@ -0,0 +1,6 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr_cfg
+---
+Star
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__rename-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__rename-2.snap
new file mode 100644
index 0000000..2bb6693
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__rename-2.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr
+---
+pattern:
+ replacement: foobar_
+ insertion_index: 7
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__rename.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__rename.snap
new file mode 100644
index 0000000..2bb6693
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__attrs__tests__rename.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/ast/attrs.rs
+expression: attr
+---
+pattern:
+ replacement: foobar_
+ insertion_index: 7
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__enums__tests__enum_with_discr.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__enums__tests__enum_with_discr.snap
new file mode 100644
index 0000000..56d6eaf
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__enums__tests__enum_with_discr.snap
@@ -0,0 +1,37 @@
+---
+source: core/src/ast/enums.rs
+expression: "Enum::new(&syn::parse_quote! {\n /// Some docs.\n #[diplomat :: rust_link(foo :: Bar, Enum)] enum\n DiscriminantedEnum { Abc = - 1, Def = 0, Ghi = 1, Jkl = 2, }\n }, &Default::default())"
+---
+name: DiscriminantedEnum
+docs:
+ - Some docs.
+ - - path:
+ elements:
+ - foo
+ - Bar
+ typ: Enum
+ display: Normal
+variants:
+ - - Abc
+ - -1
+ - - ""
+ - []
+ - {}
+ - - Def
+ - 0
+ - - ""
+ - []
+ - {}
+ - - Ghi
+ - 1
+ - - ""
+ - []
+ - {}
+ - - Jkl
+ - 2
+ - - ""
+ - []
+ - {}
+methods: []
+attrs: {}
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__enums__tests__simple_enum.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__enums__tests__simple_enum.snap
new file mode 100644
index 0000000..3c509d5
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__enums__tests__simple_enum.snap
@@ -0,0 +1,27 @@
+---
+source: core/src/ast/enums.rs
+expression: "Enum::new(&syn::parse_quote! {\n /// Some docs.\n #[diplomat :: rust_link(foo :: Bar, Enum)] enum MyLocalEnum\n {\n Abc, /// Some more docs.\n Def\n }\n }, &Default::default())"
+---
+name: MyLocalEnum
+docs:
+ - Some docs.
+ - - path:
+ elements:
+ - foo
+ - Bar
+ typ: Enum
+ display: Normal
+variants:
+ - - Abc
+ - 0
+ - - ""
+ - []
+ - {}
+ - - Def
+ - 1
+ - - Some more docs.
+ - []
+ - {}
+methods: []
+attrs: {}
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__cfged_method.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__cfged_method.snap
new file mode 100644
index 0000000..245fe61
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__cfged_method.snap
@@ -0,0 +1,34 @@
+---
+source: core/src/ast/methods.rs
+expression: "Method::from_syn(&syn::parse_quote! {\n #[doc = r\" Some docs.\"]\n #[diplomat::rust_link(foo::Bar::batz, FnInStruct)]\n #[cfg(any(feature = \"foo\", not(feature = \"bar\")))] fn\n foo(x: u64, y: MyCustomStruct) {}\n },\n PathType::new(Path::empty().sub_path(Ident::from(\"MyStructContainingMethod\"))),\n None, &Attrs::default())"
+---
+name: foo
+docs:
+ - Some docs.
+ - - path:
+ elements:
+ - foo
+ - Bar
+ - batz
+ typ: FnInStruct
+ display: Normal
+abi_name: MyStructContainingMethod_foo
+self_param: ~
+params:
+ - name: x
+ ty:
+ Primitive: u64
+ attrs: {}
+ - name: y
+ ty:
+ Named:
+ path:
+ elements:
+ - MyCustomStruct
+ lifetimes: []
+ attrs: {}
+return_type: ~
+lifetime_env: {}
+attrs:
+ cfg:
+ - "# [cfg (any (feature = \"foo\" , not (feature = \"bar\")))]"
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods-2.snap
new file mode 100644
index 0000000..5dfc34c
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods-2.snap
@@ -0,0 +1,42 @@
+---
+source: core/src/ast/methods.rs
+expression: "Method::from_syn(&syn::parse_quote! {\n #[diplomat::rust_link(foo::Bar::batz, FnInStruct)] fn\n foo(&mut self, x: u64, y: MyCustomStruct) -> u64 { x }\n },\n PathType::new(Path::empty().sub_path(Ident::from(\"MyStructContainingMethod\"))),\n None, &Attrs::default())"
+---
+name: foo
+docs:
+ - ""
+ - - path:
+ elements:
+ - foo
+ - Bar
+ - batz
+ typ: FnInStruct
+ display: Normal
+abi_name: MyStructContainingMethod_foo
+self_param:
+ reference:
+ - Anonymous
+ - Mutable
+ path_type:
+ path:
+ elements:
+ - MyStructContainingMethod
+ lifetimes: []
+ attrs: {}
+params:
+ - name: x
+ ty:
+ Primitive: u64
+ attrs: {}
+ - name: y
+ ty:
+ Named:
+ path:
+ elements:
+ - MyCustomStruct
+ lifetimes: []
+ attrs: {}
+return_type:
+ Primitive: u64
+lifetime_env: {}
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods.snap
new file mode 100644
index 0000000..4147b13
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__nonstatic_methods.snap
@@ -0,0 +1,35 @@
+---
+source: core/src/ast/methods.rs
+expression: "Method::from_syn(&syn::parse_quote! {\n fn foo(&self, x: u64, y: MyCustomStruct) {}\n },\n PathType::new(Path::empty().sub_path(Ident::from(\"MyStructContainingMethod\"))),\n None, &Attrs::default())"
+---
+name: foo
+docs:
+ - ""
+ - []
+abi_name: MyStructContainingMethod_foo
+self_param:
+ reference:
+ - Anonymous
+ - Immutable
+ path_type:
+ path:
+ elements:
+ - MyStructContainingMethod
+ lifetimes: []
+ attrs: {}
+params:
+ - name: x
+ ty:
+ Primitive: u64
+ attrs: {}
+ - name: y
+ ty:
+ Named:
+ path:
+ elements:
+ - MyCustomStruct
+ lifetimes: []
+ attrs: {}
+return_type: ~
+lifetime_env: {}
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods-2.snap
new file mode 100644
index 0000000..516f511
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods-2.snap
@@ -0,0 +1,33 @@
+---
+source: core/src/ast/methods.rs
+expression: "Method::from_syn(&syn::parse_quote! {\n #[doc = r\" Some docs.\"] #[doc = r\" Some more docs.\"]\n #[doc = r\"\"] #[doc = r\" Even more docs.\"]\n #[diplomat::rust_link(foo::Bar::batz, FnInEnum)] fn\n foo(x: u64, y: MyCustomStruct) -> u64 { x }\n },\n PathType::new(Path::empty().sub_path(Ident::from(\"MyStructContainingMethod\"))),\n None, &Attrs::default())"
+---
+name: foo
+docs:
+ - "Some docs.\nSome more docs.\n\nEven more docs."
+ - - path:
+ elements:
+ - foo
+ - Bar
+ - batz
+ typ: FnInEnum
+ display: Normal
+abi_name: MyStructContainingMethod_foo
+self_param: ~
+params:
+ - name: x
+ ty:
+ Primitive: u64
+ attrs: {}
+ - name: y
+ ty:
+ Named:
+ path:
+ elements:
+ - MyCustomStruct
+ lifetimes: []
+ attrs: {}
+return_type:
+ Primitive: u64
+lifetime_env: {}
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods.snap
new file mode 100644
index 0000000..58bc55c
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__methods__tests__static_methods.snap
@@ -0,0 +1,32 @@
+---
+source: core/src/ast/methods.rs
+expression: "Method::from_syn(&syn::parse_quote! {\n #[doc = r\" Some docs.\"]\n #[diplomat::rust_link(foo::Bar::batz, FnInStruct)] fn\n foo(x: u64, y: MyCustomStruct) {}\n },\n PathType::new(Path::empty().sub_path(Ident::from(\"MyStructContainingMethod\"))),\n None, &Attrs::default())"
+---
+name: foo
+docs:
+ - Some docs.
+ - - path:
+ elements:
+ - foo
+ - Bar
+ - batz
+ typ: FnInStruct
+ display: Normal
+abi_name: MyStructContainingMethod_foo
+self_param: ~
+params:
+ - name: x
+ ty:
+ Primitive: u64
+ attrs: {}
+ - name: y
+ ty:
+ Named:
+ path:
+ elements:
+ - MyCustomStruct
+ lifetimes: []
+ attrs: {}
+return_type: ~
+lifetime_env: {}
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__import_in_non_diplomat_not_analyzed.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__import_in_non_diplomat_not_analyzed.snap
new file mode 100644
index 0000000..f7e4241
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__import_in_non_diplomat_not_analyzed.snap
@@ -0,0 +1,31 @@
+---
+source: core/src/ast/modules.rs
+expression: "File::from(&syn::parse_quote! {\n #[diplomat :: bridge] mod ffi { struct Foo {} } mod other\n { use something :: * ; }\n })"
+---
+modules:
+ ffi:
+ name: ffi
+ imports: []
+ declared_types:
+ Foo:
+ Struct:
+ name: Foo
+ docs:
+ - ""
+ - []
+ lifetimes: {}
+ fields: []
+ methods: []
+ output_only: false
+ attrs: {}
+ declared_traits: {}
+ sub_modules: []
+ attrs: {}
+ other:
+ name: other
+ imports: []
+ declared_types: {}
+ declared_traits: {}
+ sub_modules: []
+ attrs: {}
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__method_visibility.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__method_visibility.snap
new file mode 100644
index 0000000..f28031d
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__method_visibility.snap
@@ -0,0 +1,32 @@
+---
+source: core/src/ast/modules.rs
+assertion_line: 371
+expression: "Module::from_syn(&syn::parse_quote! {\n #[diplomat::bridge] mod ffi\n {\n struct Foo {} impl Foo\n {\n pub fn pub_fn() { unimplemented!() } pub(crate) fn\n pub_crate_fn() { unimplemented!() } pub(super) fn\n pub_super_fn() { unimplemented!() } fn priv_fn()\n { unimplemented!() }\n }\n }\n }, true)"
+---
+name: ffi
+imports: []
+declared_types:
+ Foo:
+ Struct:
+ name: Foo
+ docs:
+ - ""
+ - []
+ lifetimes: {}
+ fields: []
+ methods:
+ - name: pub_fn
+ docs:
+ - ""
+ - []
+ abi_name: Foo_pub_fn
+ self_param: ~
+ params: []
+ return_type: ~
+ lifetime_env: {}
+ attrs: {}
+ output_only: false
+ attrs: {}
+declared_traits: {}
+sub_modules: []
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__simple_mod.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__simple_mod.snap
new file mode 100644
index 0000000..409136a
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__modules__tests__simple_mod.snap
@@ -0,0 +1,130 @@
+---
+source: core/src/ast/modules.rs
+assertion_line: 326
+expression: "Module::from_syn(&syn::parse_quote! {\n mod ffi\n {\n struct NonOpaqueStruct { a: i32, b: Box<NonOpaqueStruct> }\n impl NonOpaqueStruct\n {\n pub fn new(x: i32) -> NonOpaqueStruct { unimplemented!(); }\n pub fn set_a(&mut self, new_a: i32) { self.a = new_a; }\n } #[diplomat::opaque] struct OpaqueStruct\n { a: SomeExternalType } impl OpaqueStruct\n {\n pub fn new() -> Box<OpaqueStruct> { unimplemented!(); } pub\n fn get_string(&self) -> String { unimplemented!() }\n }\n }\n }, true)"
+---
+name: ffi
+imports: []
+declared_types:
+ NonOpaqueStruct:
+ Struct:
+ name: NonOpaqueStruct
+ docs:
+ - ""
+ - []
+ lifetimes: {}
+ fields:
+ - - a
+ - Primitive: i32
+ - - ""
+ - []
+ - {}
+ - - b
+ - Box:
+ Named:
+ path:
+ elements:
+ - NonOpaqueStruct
+ lifetimes: []
+ - - ""
+ - []
+ - {}
+ methods:
+ - name: new
+ docs:
+ - ""
+ - []
+ abi_name: NonOpaqueStruct_new
+ self_param: ~
+ params:
+ - name: x
+ ty:
+ Primitive: i32
+ attrs: {}
+ return_type:
+ Named:
+ path:
+ elements:
+ - NonOpaqueStruct
+ lifetimes: []
+ lifetime_env: {}
+ attrs: {}
+ - name: set_a
+ docs:
+ - ""
+ - []
+ abi_name: NonOpaqueStruct_set_a
+ self_param:
+ reference:
+ - Anonymous
+ - Mutable
+ path_type:
+ path:
+ elements:
+ - NonOpaqueStruct
+ lifetimes: []
+ attrs: {}
+ params:
+ - name: new_a
+ ty:
+ Primitive: i32
+ attrs: {}
+ return_type: ~
+ lifetime_env: {}
+ attrs: {}
+ output_only: false
+ attrs: {}
+ OpaqueStruct:
+ Opaque:
+ name: OpaqueStruct
+ docs:
+ - ""
+ - []
+ lifetimes: {}
+ methods:
+ - name: new
+ docs:
+ - ""
+ - []
+ abi_name: OpaqueStruct_new
+ self_param: ~
+ params: []
+ return_type:
+ Box:
+ Named:
+ path:
+ elements:
+ - OpaqueStruct
+ lifetimes: []
+ lifetime_env: {}
+ attrs: {}
+ - name: get_string
+ docs:
+ - ""
+ - []
+ abi_name: OpaqueStruct_get_string
+ self_param:
+ reference:
+ - Anonymous
+ - Immutable
+ path_type:
+ path:
+ elements:
+ - OpaqueStruct
+ lifetimes: []
+ attrs: {}
+ params: []
+ return_type:
+ Named:
+ path:
+ elements:
+ - String
+ lifetimes: []
+ lifetime_env: {}
+ attrs: {}
+ mutability: Immutable
+ attrs: {}
+ dtor_abi_name: OpaqueStruct_destroy
+declared_traits: {}
+sub_modules: []
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__structs__tests__simple_struct.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__structs__tests__simple_struct.snap
new file mode 100644
index 0000000..e3f44b0
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__structs__tests__simple_struct.snap
@@ -0,0 +1,33 @@
+---
+source: core/src/ast/structs.rs
+expression: "Struct::new(&syn::parse_quote! {\n #[doc = r\" Some docs.\"]\n #[diplomat::rust_link(foo::Bar, Struct)] struct MyLocalStruct\n { a: i32, b: Box<MyLocalStruct> }\n }, true, &Default::default())"
+---
+name: MyLocalStruct
+docs:
+ - Some docs.
+ - - path:
+ elements:
+ - foo
+ - Bar
+ typ: Struct
+ display: Normal
+lifetimes: {}
+fields:
+ - - a
+ - Primitive: i32
+ - - ""
+ - []
+ - {}
+ - - b
+ - Box:
+ Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+ - - ""
+ - []
+ - {}
+methods: []
+output_only: true
+attrs: {}
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-2.snap
new file mode 100644
index 0000000..8a60d74
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-2.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { :: core :: my_type :: Foo }, None)"
+---
+Named:
+ path:
+ elements:
+ - core
+ - my_type
+ - Foo
+ lifetimes: []
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-3.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-3.snap
new file mode 100644
index 0000000..8aabe3d
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-3.snap
@@ -0,0 +1,13 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { :: core :: my_type :: Foo < 'test > },\n None)"
+---
+Named:
+ path:
+ elements:
+ - core
+ - my_type
+ - Foo
+ lifetimes:
+ - Named: test
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-4.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-4.snap
new file mode 100644
index 0000000..1d7f0c5
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-4.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Option<Ref<'object>> }, None)"
+---
+Option:
+ - Named:
+ path:
+ elements:
+ - Ref
+ lifetimes:
+ - Named: object
+ - Stdlib
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-5.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-5.snap
new file mode 100644
index 0000000..8c8ed1c
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-5.snap
@@ -0,0 +1,14 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Foo < 'a, 'b, 'c, 'd > }, None)"
+---
+Named:
+ path:
+ elements:
+ - Foo
+ lifetimes:
+ - Named: a
+ - Named: b
+ - Named: c
+ - Named: d
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-6.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-6.snap
new file mode 100644
index 0000000..fbbae27
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-6.snap
@@ -0,0 +1,18 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! {\n very :: long :: path :: to :: my :: Type < 'x, 'y, 'z >\n }, None)"
+---
+Named:
+ path:
+ elements:
+ - very
+ - long
+ - path
+ - to
+ - my
+ - Type
+ lifetimes:
+ - Named: x
+ - Named: y
+ - Named: z
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-7.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-7.snap
new file mode 100644
index 0000000..6bb2a47
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes-7.snap
@@ -0,0 +1,19 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Result<OkRef<'a, 'b>, ErrRef<'c>> },\n None)"
+---
+Result:
+ - Named:
+ path:
+ elements:
+ - OkRef
+ lifetimes:
+ - Named: a
+ - Named: b
+ - Named:
+ path:
+ elements:
+ - ErrRef
+ lifetimes:
+ - Named: c
+ - Stdlib
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes.snap
new file mode 100644
index 0000000..41047ac
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__lifetimes.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Foo < 'a, 'b > }, None)"
+---
+Named:
+ path:
+ elements:
+ - Foo
+ lifetimes:
+ - Named: a
+ - Named: b
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes-2.snap
new file mode 100644
index 0000000..18a72a2
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes-2.snap
@@ -0,0 +1,11 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Box < MyLocalStruct > }, None)"
+---
+Box:
+ Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes.snap
new file mode 100644
index 0000000..30d9ae3
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_boxes.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Box < i32 > }, None)"
+---
+Box:
+ Primitive: i32
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_named.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_named.snap
new file mode 100644
index 0000000..f7763c5
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_named.snap
@@ -0,0 +1,10 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { MyLocalStruct }, None)"
+---
+Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_option-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_option-2.snap
new file mode 100644
index 0000000..b1136d0
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_option-2.snap
@@ -0,0 +1,11 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Option<MyLocalStruct> }, None)"
+---
+Option:
+ - Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+ - Stdlib
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_option.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_option.snap
new file mode 100644
index 0000000..0cfd954
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_option.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Option<i32> }, None)"
+---
+Option:
+ - Primitive: i32
+ - Stdlib
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-2.snap
new file mode 100644
index 0000000..3887727
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-2.snap
@@ -0,0 +1,6 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { usize }, None)"
+---
+Primitive: usize
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-3.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-3.snap
new file mode 100644
index 0000000..2fac7f4
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives-3.snap
@@ -0,0 +1,6 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { bool }, None)"
+---
+Primitive: bool
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives.snap
new file mode 100644
index 0000000..5ef0eaf
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_primitives.snap
@@ -0,0 +1,6 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { i32 }, None)"
+---
+Primitive: i32
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_references-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_references-2.snap
new file mode 100644
index 0000000..396affe
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_references-2.snap
@@ -0,0 +1,13 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { & mut MyLocalStruct }, None)"
+---
+Reference:
+ - Anonymous
+ - Mutable
+ - Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_references.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_references.snap
new file mode 100644
index 0000000..38c72a4
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_references.snap
@@ -0,0 +1,9 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { & i32 }, None)"
+---
+Reference:
+ - Anonymous
+ - Immutable
+ - Primitive: i32
+
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-2.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-2.snap
new file mode 100644
index 0000000..a3c187a
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-2.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { DiplomatResult<(), MyLocalStruct> },\n None)"
+---
+Result:
+ - Unit
+ - Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+ - Diplomat
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-3.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-3.snap
new file mode 100644
index 0000000..e83fdda
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-3.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Result<MyLocalStruct, i32> }, None)"
+---
+Result:
+ - Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+ - Primitive: i32
+ - Stdlib
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-4.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-4.snap
new file mode 100644
index 0000000..54ddc98
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result-4.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { Result<(), MyLocalStruct> }, None)"
+---
+Result:
+ - Unit
+ - Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+ - Stdlib
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result.snap
new file mode 100644
index 0000000..dc28b42
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__types__tests__typename_result.snap
@@ -0,0 +1,12 @@
+---
+source: core/src/ast/types.rs
+expression: "TypeName::from_syn(&syn::parse_quote! { DiplomatResult<MyLocalStruct, i32> },\n None)"
+---
+Result:
+ - Named:
+ path:
+ elements:
+ - MyLocalStruct
+ lifetimes: []
+ - Primitive: i32
+ - Diplomat
diff --git a/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__validity__tests__lifetime_in_return.snap b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__validity__tests__lifetime_in_return.snap
new file mode 100644
index 0000000..8a00617
--- /dev/null
+++ b/crates/diplomat_core/src/ast/snapshots/diplomat_core__ast__validity__tests__lifetime_in_return.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/ast/validity.rs
+expression: output
+---
+A return type contains elided lifetimes, which aren't yet supported: &Opaque in &Opaque
+A return type contains elided lifetimes, which aren't yet supported: Foo in Foo
+
diff --git a/crates/diplomat_core/src/ast/structs.rs b/crates/diplomat_core/src/ast/structs.rs
new file mode 100644
index 0000000..ab0681a
--- /dev/null
+++ b/crates/diplomat_core/src/ast/structs.rs
@@ -0,0 +1,120 @@
+use serde::Serialize;
+
+use super::docs::Docs;
+use super::{Attrs, Ident, LifetimeEnv, Method, Mutability, PathType, TypeName};
+
+/// A struct declaration in an FFI module that is not opaque.
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
+#[non_exhaustive]
+pub struct Struct {
+ pub name: Ident,
+ pub docs: Docs,
+ pub lifetimes: LifetimeEnv,
+ pub fields: Vec<(Ident, TypeName, Docs, Attrs)>,
+ pub methods: Vec<Method>,
+ pub output_only: bool,
+ pub attrs: Attrs,
+}
+
+impl Struct {
+ /// Extract a [`Struct`] metadata value from an AST node.
+ pub fn new(strct: &syn::ItemStruct, output_only: bool, parent_attrs: &Attrs) -> Self {
+ let self_path_type = PathType::extract_self_type(strct);
+ let fields: Vec<_> = strct
+ .fields
+ .iter()
+ .map(|field| {
+ // Non-opaque tuple structs will never be allowed
+ let name = field
+ .ident
+ .as_ref()
+ .map(Into::into)
+ .expect("non-opaque tuples structs are disallowed");
+ let type_name = TypeName::from_syn(&field.ty, Some(self_path_type.clone()));
+ let docs = Docs::from_attrs(&field.attrs);
+
+ (name, type_name, docs, Attrs::from_attrs(&field.attrs))
+ })
+ .collect();
+
+ let lifetimes = LifetimeEnv::from_struct_item(strct, &fields[..]);
+ let mut attrs = parent_attrs.clone();
+ attrs.add_attrs(&strct.attrs);
+ Struct {
+ name: (&strct.ident).into(),
+ docs: Docs::from_attrs(&strct.attrs),
+ lifetimes,
+ fields,
+ methods: vec![],
+ output_only,
+ attrs,
+ }
+ }
+}
+
+/// A struct annotated with [`diplomat::opaque`] whose fields are not visible.
+/// Opaque structs cannot be passed by-value across the FFI boundary, so they
+/// must be boxed or passed as references.
+#[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)]
+#[non_exhaustive]
+pub struct OpaqueStruct {
+ pub name: Ident,
+ pub docs: Docs,
+ pub lifetimes: LifetimeEnv,
+ pub methods: Vec<Method>,
+ pub mutability: Mutability,
+ pub attrs: Attrs,
+ /// The ABI name of the generated destructor
+ pub dtor_abi_name: Ident,
+}
+
+impl OpaqueStruct {
+ /// Extract a [`OpaqueStruct`] metadata value from an AST node.
+ pub fn new(strct: &syn::ItemStruct, mutability: Mutability, parent_attrs: &Attrs) -> Self {
+ let mut attrs = parent_attrs.clone();
+ attrs.add_attrs(&strct.attrs);
+ let name = Ident::from(&strct.ident);
+ let dtor_abi_name = format!("{}_destroy", name);
+ let dtor_abi_name = String::from(attrs.abi_rename.apply(dtor_abi_name.into()));
+ let dtor_abi_name = Ident::from(dtor_abi_name);
+ OpaqueStruct {
+ name,
+ docs: Docs::from_attrs(&strct.attrs),
+ lifetimes: LifetimeEnv::from_struct_item(strct, &[]),
+ methods: vec![],
+ mutability,
+ attrs,
+ dtor_abi_name,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta::{self, Settings};
+
+ use syn;
+
+ use super::Struct;
+
+ #[test]
+ fn simple_struct() {
+ let mut settings = Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_yaml_snapshot!(Struct::new(
+ &syn::parse_quote! {
+ /// Some docs.
+ #[diplomat::rust_link(foo::Bar, Struct)]
+ struct MyLocalStruct {
+ a: i32,
+ b: Box<MyLocalStruct>
+ }
+ },
+ true,
+ &Default::default()
+ ));
+ });
+ }
+}
diff --git a/crates/diplomat_core/src/ast/traits.rs b/crates/diplomat_core/src/ast/traits.rs
new file mode 100644
index 0000000..4b7ef09
--- /dev/null
+++ b/crates/diplomat_core/src/ast/traits.rs
@@ -0,0 +1,117 @@
+use serde::Serialize;
+
+use super::docs::Docs;
+use super::{Attrs, Ident, LifetimeEnv, Param, PathType, TraitSelfParam, TypeName};
+
+/// A trait declaration in an FFI module.
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
+#[non_exhaustive]
+pub struct Trait {
+ pub name: Ident,
+ pub lifetimes: LifetimeEnv,
+ pub methods: Vec<TraitMethod>,
+ pub docs: Docs,
+ pub attrs: Attrs,
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)]
+#[non_exhaustive]
+pub struct TraitMethod {
+ pub name: Ident,
+ pub abi_name: Ident,
+ pub self_param: Option<TraitSelfParam>,
+ // corresponds to the types in Function(Vec<Box<TypeName>>, Box<TypeName>)
+ // the callback type; except here the params aren't anonymous
+ pub params: Vec<Param>,
+ pub output_type: Option<TypeName>,
+ pub lifetimes: LifetimeEnv,
+ pub attrs: Attrs,
+ pub docs: Docs,
+}
+
+impl Trait {
+ /// Extract a [`Trait`] metadata value from an AST node.
+ pub fn new(trt: &syn::ItemTrait, parent_attrs: &Attrs) -> Self {
+ let mut attrs = parent_attrs.clone();
+ attrs.add_attrs(&trt.attrs);
+
+ let mut trait_fcts = Vec::new();
+
+ let self_ident = &trt.ident;
+ // TODO check this
+ let self_path_trait = PathType::from(&syn::TraitBound {
+ paren_token: None,
+ modifier: syn::TraitBoundModifier::None,
+ lifetimes: None, // todo this is an assumption
+ path: syn::PathSegment {
+ ident: self_ident.clone(),
+ arguments: syn::PathArguments::None,
+ }
+ .into(),
+ });
+ for trait_item in trt.items.iter() {
+ if let syn::TraitItem::Fn(fct) = trait_item {
+ let mut fct_attrs = attrs.clone();
+ fct_attrs.add_attrs(&fct.attrs);
+ // copied from the method parsing
+ let fct_ident = &fct.sig.ident;
+ let concat_fct_ident = format!("{self_ident}_{fct_ident}");
+ let extern_ident = syn::Ident::new(
+ &attrs.abi_rename.apply(concat_fct_ident.into()),
+ fct.sig.ident.span(),
+ );
+
+ let all_params = fct
+ .sig
+ .inputs
+ .iter()
+ .filter_map(|a| match a {
+ syn::FnArg::Receiver(_) => None,
+ syn::FnArg::Typed(ref t) => {
+ Some(Param::from_syn(t, self_path_trait.clone()))
+ }
+ })
+ .collect::<Vec<_>>();
+
+ let self_param = fct
+ .sig
+ .receiver()
+ .map(|rec| TraitSelfParam::from_syn(rec, self_path_trait.clone()));
+
+ let output_type = match &fct.sig.output {
+ syn::ReturnType::Type(_, return_typ) => Some(TypeName::from_syn(
+ return_typ.as_ref(),
+ Some(self_path_trait.clone()),
+ )),
+ syn::ReturnType::Default => None,
+ };
+
+ let lifetimes = LifetimeEnv::from_trait_item(
+ trait_item,
+ self_param.as_ref(),
+ &all_params[..],
+ output_type.as_ref(),
+ );
+
+ trait_fcts.push(TraitMethod {
+ name: fct_ident.into(),
+ abi_name: (&extern_ident).into(),
+ self_param,
+ params: all_params,
+ output_type,
+ lifetimes,
+ attrs: fct_attrs,
+ docs: Docs::from_attrs(&fct.attrs),
+ });
+ }
+ }
+
+ Self {
+ name: (&trt.ident).into(),
+ methods: trait_fcts,
+ docs: Docs::from_attrs(&trt.attrs),
+ lifetimes: LifetimeEnv::from_trait(trt), // TODO
+ attrs,
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/ast/types.rs b/crates/diplomat_core/src/ast/types.rs
new file mode 100644
index 0000000..72594ac
--- /dev/null
+++ b/crates/diplomat_core/src/ast/types.rs
@@ -0,0 +1,1658 @@
+use proc_macro2::Span;
+use quote::{ToTokens, TokenStreamExt};
+use serde::{Deserialize, Serialize};
+use syn::Token;
+
+use std::fmt;
+use std::ops::ControlFlow;
+use std::str::FromStr;
+
+use super::{
+ Attrs, Docs, Enum, Ident, Lifetime, LifetimeEnv, LifetimeTransitivity, Method, NamedLifetime,
+ OpaqueStruct, Path, RustLink, Struct, Trait,
+};
+use crate::Env;
+
+/// A type declared inside a Diplomat-annotated module.
+#[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum CustomType {
+ /// A non-opaque struct whose fields will be visible across the FFI boundary.
+ Struct(Struct),
+ /// A struct annotated with [`diplomat::opaque`] whose fields are not visible.
+ Opaque(OpaqueStruct),
+ /// A fieldless enum.
+ Enum(Enum),
+}
+
+impl CustomType {
+ /// Get the name of the custom type, which is unique within a module.
+ pub fn name(&self) -> &Ident {
+ match self {
+ CustomType::Struct(strct) => &strct.name,
+ CustomType::Opaque(strct) => &strct.name,
+ CustomType::Enum(enm) => &enm.name,
+ }
+ }
+
+ /// Get the methods declared in impls of the custom type.
+ pub fn methods(&self) -> &Vec<Method> {
+ match self {
+ CustomType::Struct(strct) => &strct.methods,
+ CustomType::Opaque(strct) => &strct.methods,
+ CustomType::Enum(enm) => &enm.methods,
+ }
+ }
+
+ pub fn attrs(&self) -> &Attrs {
+ match self {
+ CustomType::Struct(strct) => &strct.attrs,
+ CustomType::Opaque(strct) => &strct.attrs,
+ CustomType::Enum(enm) => &enm.attrs,
+ }
+ }
+
+ /// Get the doc lines of the custom type.
+ pub fn docs(&self) -> &Docs {
+ match self {
+ CustomType::Struct(strct) => &strct.docs,
+ CustomType::Opaque(strct) => &strct.docs,
+ CustomType::Enum(enm) => &enm.docs,
+ }
+ }
+
+ /// Get all rust links on this type and its methods
+ pub fn all_rust_links(&self) -> impl Iterator<Item = &RustLink> + '_ {
+ [self.docs()]
+ .into_iter()
+ .chain(self.methods().iter().map(|m| m.docs()))
+ .flat_map(|d| d.rust_links().iter())
+ }
+
+ pub fn self_path(&self, in_path: &Path) -> Path {
+ in_path.sub_path(self.name().clone())
+ }
+
+ /// Get the lifetimes of the custom type.
+ pub fn lifetimes(&self) -> Option<&LifetimeEnv> {
+ match self {
+ CustomType::Struct(strct) => Some(&strct.lifetimes),
+ CustomType::Opaque(strct) => Some(&strct.lifetimes),
+ CustomType::Enum(_) => None,
+ }
+ }
+}
+
+/// A symbol declared in a module, which can either be a pointer to another path,
+/// or a custom type defined directly inside that module
+#[derive(Clone, Serialize, Debug)]
+#[non_exhaustive]
+pub enum ModSymbol {
+ /// A symbol that is a pointer to another path.
+ Alias(Path),
+ /// A symbol that is a submodule.
+ SubModule(Ident),
+ /// A symbol that is a custom type.
+ CustomType(CustomType),
+ /// A trait
+ Trait(Trait),
+}
+
+/// A named type that is just a path, e.g. `std::borrow::Cow<'a, T>`.
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
+#[non_exhaustive]
+pub struct PathType {
+ pub path: Path,
+ pub lifetimes: Vec<Lifetime>,
+}
+
+impl PathType {
+ pub fn to_syn(&self) -> syn::TypePath {
+ let mut path = self.path.to_syn();
+
+ if !self.lifetimes.is_empty() {
+ if let Some(seg) = path.segments.last_mut() {
+ let lifetimes = &self.lifetimes;
+ seg.arguments =
+ syn::PathArguments::AngleBracketed(syn::parse_quote! { <#(#lifetimes),*> });
+ }
+ }
+
+ syn::TypePath { qself: None, path }
+ }
+
+ pub fn new(path: Path) -> Self {
+ Self {
+ path,
+ lifetimes: vec![],
+ }
+ }
+
+ /// Get the `Self` type from a struct declaration.
+ ///
+ /// Consider the following struct declaration:
+ /// ```
+ /// struct RefList<'a> {
+ /// data: &'a i32,
+ /// next: Option<Box<Self>>,
+ /// }
+ /// ```
+ /// When determining what type `Self` is in the `next` field, we would have to call
+ /// this method on the `syn::ItemStruct` that represents this struct declaration.
+ /// This method would then return a `PathType` representing `RefList<'a>`, so we
+ /// know that's what `Self` should refer to.
+ ///
+ /// The reason this function exists though is so when we convert the fields' types
+ /// to `PathType`s, we don't panic. We don't actually need to write the struct's
+ /// field types expanded in the macro, so this function is more for correctness,
+ pub fn extract_self_type(strct: &syn::ItemStruct) -> Self {
+ let self_name = (&strct.ident).into();
+
+ PathType {
+ path: Path {
+ elements: vec![self_name],
+ },
+ lifetimes: strct
+ .generics
+ .lifetimes()
+ .map(|lt_def| (<_def.lifetime).into())
+ .collect(),
+ }
+ }
+
+ /// If this is a [`TypeName::Named`], grab the [`CustomType`] it points to from
+ /// the `env`, which contains all [`CustomType`]s across all FFI modules.
+ ///
+ /// Also returns the path the CustomType is in (useful for resolving fields)
+ pub fn resolve_with_path<'a>(&self, in_path: &Path, env: &'a Env) -> (Path, &'a CustomType) {
+ let local_path = &self.path;
+ let mut cur_path = in_path.clone();
+ for (i, elem) in local_path.elements.iter().enumerate() {
+ match elem.as_str() {
+ "crate" => {
+ // TODO(#34): get the name of enclosing crate from env when we support multiple crates
+ cur_path = Path::empty()
+ }
+
+ "super" => cur_path = cur_path.get_super(),
+
+ o => match env.get(&cur_path, o) {
+ Some(ModSymbol::Alias(p)) => {
+ let mut remaining_elements: Vec<Ident> =
+ local_path.elements.iter().skip(i + 1).cloned().collect();
+ let mut new_path = p.elements.clone();
+ new_path.append(&mut remaining_elements);
+ return PathType::new(Path { elements: new_path })
+ .resolve_with_path(&cur_path.clone(), env);
+ }
+ Some(ModSymbol::SubModule(name)) => {
+ cur_path.elements.push(name.clone());
+ }
+ Some(ModSymbol::CustomType(t)) => {
+ if i == local_path.elements.len() - 1 {
+ return (cur_path, t);
+ } else {
+ panic!(
+ "Unexpected custom type when resolving symbol {} in {}",
+ o,
+ cur_path.elements.join("::")
+ )
+ }
+ }
+ Some(ModSymbol::Trait(trt)) => {
+ panic!("Found trait {} but expected a type", trt.name);
+ }
+ None => panic!(
+ "Could not resolve symbol {} in {}",
+ o,
+ cur_path.elements.join("::")
+ ),
+ },
+ }
+ }
+
+ panic!(
+ "Path {} does not point to a custom type",
+ in_path.elements.join("::")
+ )
+ }
+
+ /// If this is a [`TypeName::Named`], grab the [`CustomType`] it points to from
+ /// the `env`, which contains all [`CustomType`]s across all FFI modules.
+ ///
+ /// If you need to resolve struct fields later, call [`Self::resolve_with_path()`] instead
+ /// to get the path to resolve the fields in.
+ pub fn resolve<'a>(&self, in_path: &Path, env: &'a Env) -> &'a CustomType {
+ self.resolve_with_path(in_path, env).1
+ }
+
+ pub fn trait_to_syn(&self) -> syn::TraitBound {
+ let mut path = self.path.to_syn();
+
+ if !self.lifetimes.is_empty() {
+ if let Some(seg) = path.segments.last_mut() {
+ let lifetimes = &self.lifetimes;
+ seg.arguments =
+ syn::PathArguments::AngleBracketed(syn::parse_quote! { <#(#lifetimes),*> });
+ }
+ }
+ syn::TraitBound {
+ paren_token: None,
+ modifier: syn::TraitBoundModifier::None,
+ lifetimes: None, // todo this is an assumption
+ path,
+ }
+ }
+
+ pub fn resolve_trait_with_path<'a>(&self, in_path: &Path, env: &'a Env) -> (Path, Trait) {
+ let local_path = &self.path;
+ let cur_path = in_path.clone();
+ for (i, elem) in local_path.elements.iter().enumerate() {
+ if let Some(ModSymbol::Trait(trt)) = env.get(&cur_path, elem.as_str()) {
+ if i == local_path.elements.len() - 1 {
+ return (cur_path, trt.clone());
+ } else {
+ panic!(
+ "Unexpected custom trait when resolving symbol {} in {}",
+ trt.name,
+ cur_path.elements.join("::")
+ )
+ }
+ }
+ }
+
+ panic!(
+ "Path {} does not point to a custom trait",
+ in_path.elements.join("::")
+ )
+ }
+
+ /// If this is a [`TypeName::Named`], grab the [`CustomType`] it points to from
+ /// the `env`, which contains all [`CustomType`]s across all FFI modules.
+ ///
+ /// If you need to resolve struct fields later, call [`Self::resolve_with_path()`] instead
+ /// to get the path to resolve the fields in.
+ pub fn resolve_trait<'a>(&self, in_path: &Path, env: &'a Env) -> Trait {
+ self.resolve_trait_with_path(in_path, env).1
+ }
+}
+
+impl From<&syn::TypePath> for PathType {
+ fn from(other: &syn::TypePath) -> Self {
+ let lifetimes = other
+ .path
+ .segments
+ .last()
+ .and_then(|last| {
+ if let syn::PathArguments::AngleBracketed(angle_generics) = &last.arguments {
+ Some(
+ angle_generics
+ .args
+ .iter()
+ .map(|generic_arg| match generic_arg {
+ syn::GenericArgument::Lifetime(lifetime) => lifetime.into(),
+ _ => panic!("generic type arguments are unsupported {other:?}"),
+ })
+ .collect(),
+ )
+ } else {
+ None
+ }
+ })
+ .unwrap_or_default();
+
+ Self {
+ path: Path::from_syn(&other.path),
+ lifetimes,
+ }
+ }
+}
+
+impl From<&syn::TraitBound> for PathType {
+ fn from(other: &syn::TraitBound) -> Self {
+ let lifetimes = other
+ .path
+ .segments
+ .last()
+ .and_then(|last| {
+ if let syn::PathArguments::AngleBracketed(angle_generics) = &last.arguments {
+ Some(
+ angle_generics
+ .args
+ .iter()
+ .map(|generic_arg| match generic_arg {
+ syn::GenericArgument::Lifetime(lifetime) => lifetime.into(),
+ _ => panic!("generic type arguments are unsupported {other:?}"),
+ })
+ .collect(),
+ )
+ } else {
+ None
+ }
+ })
+ .unwrap_or_default();
+
+ Self {
+ path: Path::from_syn(&other.path),
+ lifetimes,
+ }
+ }
+}
+
+impl From<Path> for PathType {
+ fn from(other: Path) -> Self {
+ PathType::new(other)
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
+#[allow(clippy::exhaustive_enums)] // there are only two kinds of mutability we care about
+pub enum Mutability {
+ Mutable,
+ Immutable,
+}
+
+impl Mutability {
+ pub fn to_syn(&self) -> Option<Token![mut]> {
+ match self {
+ Mutability::Mutable => Some(syn::token::Mut(Span::call_site())),
+ Mutability::Immutable => None,
+ }
+ }
+
+ pub fn from_syn(t: &Option<Token![mut]>) -> Self {
+ match t {
+ Some(_) => Mutability::Mutable,
+ None => Mutability::Immutable,
+ }
+ }
+
+ /// Returns `true` if `&self` is the mutable variant, otherwise `false`.
+ pub fn is_mutable(&self) -> bool {
+ matches!(self, Mutability::Mutable)
+ }
+
+ /// Returns `true` if `&self` is the immutable variant, otherwise `false`.
+ pub fn is_immutable(&self) -> bool {
+ matches!(self, Mutability::Immutable)
+ }
+
+ /// Shorthand ternary operator for choosing a value based on whether
+ /// a `Mutability` is mutable or immutable.
+ ///
+ /// The following pattern (with very slight variations) shows up often in code gen:
+ /// ```ignore
+ /// if mutability.is_mutable() {
+ /// ""
+ /// } else {
+ /// "const "
+ /// }
+ /// ```
+ /// This is particularly annoying in `write!(...)` statements, where `cargo fmt`
+ /// expands it to take up 5 lines.
+ ///
+ /// This method offers a 1-line alternative:
+ /// ```ignore
+ /// mutability.if_mut_else("", "const ")
+ /// ```
+ /// For cases where lazy evaluation is desired, consider using a conditional
+ /// or a `match` statement.
+ pub fn if_mut_else<T>(&self, if_mut: T, if_immut: T) -> T {
+ match self {
+ Mutability::Mutable => if_mut,
+ Mutability::Immutable => if_immut,
+ }
+ }
+}
+
+/// For types like `Result`/`DiplomatResult`, `&[T]`/`DiplomatSlice<T>` which can be
+/// specified using (non-ffi-safe) Rust stdlib types, or FFI-safe `repr(C)` types from
+/// `diplomat_runtime`, this tracks which of the two were used.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
+#[allow(clippy::exhaustive_enums)] // This can only have two values
+pub enum StdlibOrDiplomat {
+ Stdlib,
+ Diplomat,
+}
+
+/// A local type reference, such as the type of a field, parameter, or return value.
+/// Unlike [`CustomType`], which represents a type declaration, [`TypeName`]s can compose
+/// types through references and boxing, and can also capture unresolved paths.
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
+#[non_exhaustive]
+pub enum TypeName {
+ /// A built-in Rust scalar primitive.
+ Primitive(PrimitiveType),
+ /// An unresolved path to a custom type, which can be resolved after all types
+ /// are collected with [`TypeName::resolve()`].
+ Named(PathType),
+ /// An optionally mutable reference to another type.
+ Reference(Lifetime, Mutability, Box<TypeName>),
+ /// A `Box<T>` type.
+ Box(Box<TypeName>),
+ /// An `Option<T>` or DiplomatOption type.
+ Option(Box<TypeName>, StdlibOrDiplomat),
+ /// A `Result<T, E>` or `diplomat_runtime::DiplomatResult` type.
+ Result(Box<TypeName>, Box<TypeName>, StdlibOrDiplomat),
+ Write,
+ /// A `&DiplomatStr` or `Box<DiplomatStr>` type.
+ /// Owned strings don't have a lifetime.
+ ///
+ /// If StdlibOrDiplomat::Stdlib, it's specified using Rust pointer types (&T, Box<T>),
+ /// if StdlibOrDiplomat::Diplomat, it's specified using DiplomatStrSlice, etc
+ StrReference(Option<Lifetime>, StringEncoding, StdlibOrDiplomat),
+ /// A `&[T]` or `Box<[T]>` type, where `T` is a primitive.
+ /// Owned slices don't have a lifetime or mutability.
+ ///
+ /// If StdlibOrDiplomat::Stdlib, it's specified using Rust pointer types (&T, Box<T>),
+ /// if StdlibOrDiplomat::Diplomat, it's specified using DiplomatSlice/DiplomatOwnedSlice/DiplomatSliceMut
+ PrimitiveSlice(
+ Option<(Lifetime, Mutability)>,
+ PrimitiveType,
+ StdlibOrDiplomat,
+ ),
+ /// `&[&DiplomatStr]`, etc. Equivalent to `&[&str]`
+ ///
+ /// If StdlibOrDiplomat::Stdlib, it's specified as `&[&DiplomatFoo]`, if StdlibOrDiplomat::Diplomat it's specified
+ /// as `DiplomatSlice<&DiplomatFoo>`
+ StrSlice(StringEncoding, StdlibOrDiplomat),
+ /// The `()` type.
+ Unit,
+ /// The `Self` type.
+ SelfType(PathType),
+ /// std::cmp::Ordering or core::cmp::Ordering
+ ///
+ /// The path must be present! Ordering will be parsed as an AST type!
+ Ordering,
+ Function(Vec<Box<TypeName>>, Box<TypeName>),
+ ImplTrait(PathType),
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug, Copy)]
+#[non_exhaustive]
+pub enum StringEncoding {
+ UnvalidatedUtf8,
+ UnvalidatedUtf16,
+ /// The caller guarantees that they're passing valid UTF-8, under penalty of UB
+ Utf8,
+}
+
+impl StringEncoding {
+ /// Get the diplomat slice type when specified using diplomat_runtime types
+ pub fn get_diplomat_slice_type(self, lt: &Option<Lifetime>) -> syn::Type {
+ if let Some(ref lt) = *lt {
+ let lt = LifetimeGenericsListDisplay(lt);
+
+ match self {
+ Self::UnvalidatedUtf8 => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatStrSlice #lt)
+ }
+ Self::UnvalidatedUtf16 => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatStr16Slice #lt)
+ }
+ Self::Utf8 => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatUtf8StrSlice #lt)
+ }
+ }
+ } else {
+ match self {
+ Self::UnvalidatedUtf8 => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatOwnedStrSlice)
+ }
+ Self::UnvalidatedUtf16 => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatOwnedStr16Slice)
+ }
+ Self::Utf8 => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatOwnedUTF8StrSlice)
+ }
+ }
+ }
+ }
+
+ fn get_diplomat_slice_type_str(self) -> &'static str {
+ match self {
+ StringEncoding::Utf8 => "str",
+ StringEncoding::UnvalidatedUtf8 => "DiplomatStr",
+ StringEncoding::UnvalidatedUtf16 => "DiplomatStr16",
+ }
+ }
+ /// Get slice type when specified using rust stdlib types
+ pub fn get_stdlib_slice_type(self, lt: &Option<Lifetime>) -> syn::Type {
+ let inner = match self {
+ Self::UnvalidatedUtf8 => quote::quote!(DiplomatStr),
+ Self::UnvalidatedUtf16 => quote::quote!(DiplomatStr16),
+ Self::Utf8 => quote::quote!(str),
+ };
+ if let Some(ref lt) = *lt {
+ let lt = ReferenceDisplay(lt, &Mutability::Immutable);
+
+ syn::parse_quote_spanned!(Span::call_site() => #lt #inner)
+ } else {
+ syn::parse_quote_spanned!(Span::call_site() => Box<#inner>)
+ }
+ }
+ pub fn get_stdlib_slice_type_str(self) -> &'static str {
+ match self {
+ StringEncoding::Utf8 => "DiplomatUtf8Str",
+ StringEncoding::UnvalidatedUtf8 => "DiplomatStrSlice",
+ StringEncoding::UnvalidatedUtf16 => "DiplomatStr16Slice",
+ }
+ }
+}
+
+fn get_lifetime_from_syn_path(p: &syn::TypePath) -> Lifetime {
+ if let syn::PathArguments::AngleBracketed(ref generics) =
+ p.path.segments[p.path.segments.len() - 1].arguments
+ {
+ if let Some(syn::GenericArgument::Lifetime(lt)) = generics.args.first() {
+ return Lifetime::from(lt);
+ }
+ }
+ Lifetime::Anonymous
+}
+
+fn get_ty_from_syn_path(p: &syn::TypePath) -> Option<&syn::Type> {
+ if let syn::PathArguments::AngleBracketed(ref generics) =
+ p.path.segments[p.path.segments.len() - 1].arguments
+ {
+ for gen in generics.args.iter() {
+ if let syn::GenericArgument::Type(ref ty) = gen {
+ return Some(ty);
+ }
+ }
+ }
+ None
+}
+
+impl TypeName {
+ /// Is this type safe to be passed across the FFI boundary?
+ ///
+ /// This also marks DiplomatOption<&T> as FFI-unsafe: these are technically safe from an ABI standpoint
+ /// however Diplomat always expects these to be equivalent to a nullable pointer, so Option<&T> is required.
+ pub fn is_ffi_safe(&self) -> bool {
+ match self {
+ TypeName::Primitive(..) | TypeName::Named(_) | TypeName::SelfType(_) | TypeName::Reference(..) |
+ TypeName::Box(..) |
+ // can only be passed across the FFI boundary; callbacks and traits are input-only
+ TypeName::Function(..) | TypeName::ImplTrait(..) |
+ // These are specified using FFI-safe diplomat_runtime types
+ TypeName::StrReference(.., StdlibOrDiplomat::Diplomat) | TypeName::StrSlice(.., StdlibOrDiplomat::Diplomat) |TypeName::PrimitiveSlice(.., StdlibOrDiplomat::Diplomat) => true,
+ // These are special anyway and shouldn't show up in structs
+ TypeName::Unit | TypeName::Write | TypeName::Result(..) |
+ // This is basically only useful in return types
+ TypeName::Ordering |
+ // These are specified using Rust stdlib types and not safe across FFI
+ TypeName::StrReference(.., StdlibOrDiplomat::Stdlib) | TypeName::StrSlice(.., StdlibOrDiplomat::Stdlib) | TypeName::PrimitiveSlice(.., StdlibOrDiplomat::Stdlib) => false,
+ TypeName::Option(inner, stdlib) => match **inner {
+ // Option<&T>/Option<Box<T>> are the ffi-safe way to specify options
+ TypeName::Reference(..) | TypeName::Box(..) => *stdlib == StdlibOrDiplomat::Stdlib,
+ // For other types (primitives, structs, enums) we need DiplomatOption
+ _ => *stdlib == StdlibOrDiplomat::Diplomat,
+ }
+ }
+ }
+
+ /// What's the FFI safe version of this type?
+ ///
+ /// This also marks DiplomatOption<&T> as FFI-unsafe: these are technically safe from an ABI standpoint
+ /// however Diplomat always expects these to be equivalent to a nullable pointer, so Option<&T> is required.
+ pub fn ffi_safe_version(&self) -> TypeName {
+ match self {
+ TypeName::StrReference(lt, encoding, StdlibOrDiplomat::Stdlib) => {
+ TypeName::StrReference(lt.clone(), *encoding, StdlibOrDiplomat::Diplomat)
+ }
+ TypeName::StrSlice(encoding, StdlibOrDiplomat::Stdlib) => {
+ TypeName::StrSlice(*encoding, StdlibOrDiplomat::Diplomat)
+ }
+ TypeName::PrimitiveSlice(ltmt, prim, StdlibOrDiplomat::Stdlib) => {
+ TypeName::PrimitiveSlice(ltmt.clone(), *prim, StdlibOrDiplomat::Diplomat)
+ }
+ TypeName::Ordering => TypeName::Primitive(PrimitiveType::i8),
+ TypeName::Option(inner, _stdlib) => match **inner {
+ // Option<&T>/Option<Box<T>> are the ffi-safe way to specify options
+ TypeName::Reference(..) | TypeName::Box(..) => {
+ TypeName::Option(inner.clone(), StdlibOrDiplomat::Stdlib)
+ }
+ // For other types (primitives, structs, enums) we need DiplomatOption
+ _ => TypeName::Option(inner.clone(), StdlibOrDiplomat::Diplomat),
+ },
+ _ => self.clone(),
+ }
+ }
+ /// Converts the [`TypeName`] back into an AST node that can be spliced into a program.
+ pub fn to_syn(&self) -> syn::Type {
+ match self {
+ TypeName::Primitive(primitive) => {
+ let primitive = primitive.to_ident();
+ syn::parse_quote_spanned!(Span::call_site() => #primitive)
+ }
+ TypeName::Ordering => syn::parse_quote_spanned!(Span::call_site() => i8),
+ TypeName::Named(name) | TypeName::SelfType(name) => {
+ // Self also gets expanded instead of turning into `Self` because
+ // this code is used to generate the `extern "C"` functions, which
+ // aren't in an impl block.
+ let name = name.to_syn();
+ syn::parse_quote_spanned!(Span::call_site() => #name)
+ }
+ TypeName::Reference(lifetime, mutability, underlying) => {
+ let reference = ReferenceDisplay(lifetime, mutability);
+ let underlying = underlying.to_syn();
+
+ syn::parse_quote_spanned!(Span::call_site() => #reference #underlying)
+ }
+ TypeName::Box(underlying) => {
+ let underlying = underlying.to_syn();
+ syn::parse_quote_spanned!(Span::call_site() => Box<#underlying>)
+ }
+ TypeName::Option(underlying, StdlibOrDiplomat::Stdlib) => {
+ let underlying = underlying.to_syn();
+ syn::parse_quote_spanned!(Span::call_site() => Option<#underlying>)
+ }
+ TypeName::Option(underlying, StdlibOrDiplomat::Diplomat) => {
+ let underlying = underlying.to_syn();
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatOption<#underlying>)
+ }
+ TypeName::Result(ok, err, StdlibOrDiplomat::Stdlib) => {
+ let ok = ok.to_syn();
+ let err = err.to_syn();
+ syn::parse_quote_spanned!(Span::call_site() => Result<#ok, #err>)
+ }
+ TypeName::Result(ok, err, StdlibOrDiplomat::Diplomat) => {
+ let ok = ok.to_syn();
+ let err = err.to_syn();
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatResult<#ok, #err>)
+ }
+ TypeName::Write => {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatWrite)
+ }
+ TypeName::StrReference(lt, encoding, is_stdlib_type) => {
+ if *is_stdlib_type == StdlibOrDiplomat::Stdlib {
+ encoding.get_stdlib_slice_type(lt)
+ } else {
+ encoding.get_diplomat_slice_type(lt)
+ }
+ }
+ TypeName::StrSlice(encoding, is_stdlib_type) => {
+ if *is_stdlib_type == StdlibOrDiplomat::Stdlib {
+ let inner = encoding.get_stdlib_slice_type(&Some(Lifetime::Anonymous));
+ syn::parse_quote_spanned!(Span::call_site() => &[#inner])
+ } else {
+ let inner = encoding.get_diplomat_slice_type(&Some(Lifetime::Anonymous));
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatSlice<#inner>)
+ }
+ }
+ TypeName::PrimitiveSlice(ltmt, primitive, is_stdlib_type) => {
+ if *is_stdlib_type == StdlibOrDiplomat::Stdlib {
+ primitive.get_stdlib_slice_type(ltmt)
+ } else {
+ primitive.get_diplomat_slice_type(ltmt)
+ }
+ }
+
+ TypeName::Unit => syn::parse_quote_spanned!(Span::call_site() => ()),
+ TypeName::Function(_input_types, output_type) => {
+ let output_type = output_type.to_syn();
+ // should be DiplomatCallback<function_output_type>
+ syn::parse_quote_spanned!(Span::call_site() => DiplomatCallback<#output_type>)
+ }
+ TypeName::ImplTrait(trt_path) => {
+ let trait_name =
+ Ident::from(format!("DiplomatTraitStruct_{}", trt_path.path.elements[0]));
+ // should be DiplomatTraitStruct_trait_name
+ syn::parse_quote_spanned!(Span::call_site() => #trait_name)
+ }
+ }
+ }
+
+ /// Extract a [`TypeName`] from a [`syn::Type`] AST node.
+ /// The following rules are used to infer [`TypeName`] variants:
+ /// - If the type is a path with a single element that is the name of a Rust primitive, returns a [`TypeName::Primitive`]
+ /// - If the type is a path with a single element [`Box`], returns a [`TypeName::Box`] with the type parameter recursively converted
+ /// - If the type is a path with a single element [`Option`], returns a [`TypeName::Option`] with the type parameter recursively converted
+ /// - If the type is a path with a single element `Self` and `self_path_type` is provided, returns a [`TypeName::Named`]
+ /// - If the type is a path with a single element [`Result`], returns a [`TypeName::Result`] with the type parameters recursively converted
+ /// - If the type is a path equal to [`diplomat_runtime::DiplomatResult`], returns a [`TypeName::DiplomatResult`] with the type parameters recursively converted
+ /// - If the type is a path equal to [`diplomat_runtime::DiplomatWrite`], returns a [`TypeName::Write`]
+ /// - If the type is a owned or borrowed string type, returns a [`TypeName::StrReference`]
+ /// - If the type is a owned or borrowed slice of a Rust primitive, returns a [`TypeName::PrimitiveSlice`]
+ /// - If the type is a reference (`&` or `&mut`), returns a [`TypeName::Reference`] with the referenced type recursively converted
+ /// - Otherwise, assume that the reference is to a [`CustomType`] in either the current module or another one, returns a [`TypeName::Named`]
+ pub fn from_syn(ty: &syn::Type, self_path_type: Option<PathType>) -> TypeName {
+ match ty {
+ syn::Type::Reference(r) => {
+ let lifetime = Lifetime::from(&r.lifetime);
+ let mutability = Mutability::from_syn(&r.mutability);
+
+ let name = r.elem.to_token_stream().to_string();
+ if name.starts_with("DiplomatStr") || name == "str" {
+ if mutability.is_mutable() {
+ panic!("mutable string references are disallowed");
+ }
+ if name == "DiplomatStr" {
+ return TypeName::StrReference(
+ Some(lifetime),
+ StringEncoding::UnvalidatedUtf8,
+ StdlibOrDiplomat::Stdlib,
+ );
+ } else if name == "DiplomatStr16" {
+ return TypeName::StrReference(
+ Some(lifetime),
+ StringEncoding::UnvalidatedUtf16,
+ StdlibOrDiplomat::Stdlib,
+ );
+ } else if name == "str" {
+ return TypeName::StrReference(
+ Some(lifetime),
+ StringEncoding::Utf8,
+ StdlibOrDiplomat::Stdlib,
+ );
+ }
+ }
+ if let syn::Type::Slice(slice) = &*r.elem {
+ if let syn::Type::Path(p) = &*slice.elem {
+ if let Some(primitive) = p
+ .path
+ .get_ident()
+ .and_then(|i| PrimitiveType::from_str(i.to_string().as_str()).ok())
+ {
+ return TypeName::PrimitiveSlice(
+ Some((lifetime, mutability)),
+ primitive,
+ StdlibOrDiplomat::Stdlib,
+ );
+ }
+ }
+ if let TypeName::StrReference(
+ Some(Lifetime::Anonymous),
+ encoding,
+ is_stdlib_type,
+ ) = TypeName::from_syn(&slice.elem, self_path_type.clone())
+ {
+ if is_stdlib_type == StdlibOrDiplomat::Stdlib {
+ panic!("Slice-of-slice is only supported with DiplomatRuntime slice types (DiplomatStrSlice, DiplomatStr16Slice, DiplomatUtf8StrSlice)");
+ }
+ return TypeName::StrSlice(encoding, StdlibOrDiplomat::Stdlib);
+ }
+ }
+ TypeName::Reference(
+ lifetime,
+ mutability,
+ Box::new(TypeName::from_syn(r.elem.as_ref(), self_path_type)),
+ )
+ }
+ syn::Type::Path(p) => {
+ let p_len = p.path.segments.len();
+ if let Some(primitive) = p
+ .path
+ .get_ident()
+ .and_then(|i| PrimitiveType::from_str(i.to_string().as_str()).ok())
+ {
+ TypeName::Primitive(primitive)
+ } else if p_len >= 2
+ && p.path.segments[p_len - 2].ident == "cmp"
+ && p.path.segments[p_len - 1].ident == "Ordering"
+ {
+ TypeName::Ordering
+ } else if p_len == 1 && p.path.segments[0].ident == "Box" {
+ if let syn::PathArguments::AngleBracketed(type_args) =
+ &p.path.segments[0].arguments
+ {
+ if let syn::GenericArgument::Type(syn::Type::Slice(slice)) =
+ &type_args.args[0]
+ {
+ if let TypeName::Primitive(p) =
+ TypeName::from_syn(&slice.elem, self_path_type)
+ {
+ TypeName::PrimitiveSlice(None, p, StdlibOrDiplomat::Stdlib)
+ } else {
+ panic!("Owned slices only support primitives.")
+ }
+ } else if let syn::GenericArgument::Type(tpe) = &type_args.args[0] {
+ if tpe.to_token_stream().to_string() == "DiplomatStr" {
+ TypeName::StrReference(
+ None,
+ StringEncoding::UnvalidatedUtf8,
+ StdlibOrDiplomat::Stdlib,
+ )
+ } else if tpe.to_token_stream().to_string() == "DiplomatStr16" {
+ TypeName::StrReference(
+ None,
+ StringEncoding::UnvalidatedUtf16,
+ StdlibOrDiplomat::Stdlib,
+ )
+ } else if tpe.to_token_stream().to_string() == "str" {
+ TypeName::StrReference(
+ None,
+ StringEncoding::Utf8,
+ StdlibOrDiplomat::Stdlib,
+ )
+ } else {
+ TypeName::Box(Box::new(TypeName::from_syn(tpe, self_path_type)))
+ }
+ } else {
+ panic!("Expected first type argument for Box to be a type")
+ }
+ } else {
+ panic!("Expected angle brackets for Box type")
+ }
+ } else if p_len == 1 && p.path.segments[0].ident == "Option"
+ || is_runtime_type(p, "DiplomatOption")
+ {
+ if let syn::PathArguments::AngleBracketed(type_args) =
+ &p.path.segments[0].arguments
+ {
+ if let syn::GenericArgument::Type(tpe) = &type_args.args[0] {
+ let stdlib = if p.path.segments[0].ident == "Option" {
+ StdlibOrDiplomat::Stdlib
+ } else {
+ StdlibOrDiplomat::Diplomat
+ };
+ TypeName::Option(
+ Box::new(TypeName::from_syn(tpe, self_path_type)),
+ stdlib,
+ )
+ } else {
+ panic!("Expected first type argument for Option to be a type")
+ }
+ } else {
+ panic!("Expected angle brackets for Option type")
+ }
+ } else if p_len == 1 && p.path.segments[0].ident == "Self" {
+ if let Some(self_path_type) = self_path_type {
+ TypeName::SelfType(self_path_type)
+ } else {
+ panic!("Cannot have `Self` type outside of a method");
+ }
+ } else if is_runtime_type(p, "DiplomatOwnedStrSlice")
+ || is_runtime_type(p, "DiplomatOwnedStr16Slice")
+ || is_runtime_type(p, "DiplomatOwnedUTF8StrSlice")
+ {
+ let encoding = if is_runtime_type(p, "DiplomatOwnedStrSlice") {
+ StringEncoding::UnvalidatedUtf8
+ } else if is_runtime_type(p, "DiplomatOwnedStr16Slice") {
+ StringEncoding::UnvalidatedUtf16
+ } else {
+ StringEncoding::Utf8
+ };
+
+ TypeName::StrReference(None, encoding, StdlibOrDiplomat::Diplomat)
+ } else if is_runtime_type(p, "DiplomatStrSlice")
+ || is_runtime_type(p, "DiplomatStr16Slice")
+ || is_runtime_type(p, "DiplomatUtf8StrSlice")
+ {
+ let lt = get_lifetime_from_syn_path(p);
+
+ let encoding = if is_runtime_type(p, "DiplomatStrSlice") {
+ StringEncoding::UnvalidatedUtf8
+ } else if is_runtime_type(p, "DiplomatStr16Slice") {
+ StringEncoding::UnvalidatedUtf16
+ } else {
+ StringEncoding::Utf8
+ };
+
+ TypeName::StrReference(Some(lt), encoding, StdlibOrDiplomat::Diplomat)
+ } else if is_runtime_type(p, "DiplomatSlice")
+ || is_runtime_type(p, "DiplomatSliceMut")
+ || is_runtime_type(p, "DiplomatOwnedSlice")
+ {
+ let ltmut = if is_runtime_type(p, "DiplomatOwnedSlice") {
+ let mutability = if is_runtime_type(p, "DiplomatSlice") {
+ Mutability::Immutable
+ } else {
+ Mutability::Mutable
+ };
+ let lt = get_lifetime_from_syn_path(p);
+ Some((lt, mutability))
+ } else {
+ None
+ };
+
+ let ty = get_ty_from_syn_path(p).expect("Expected type argument to DiplomatSlice/DiplomatSliceMut/DiplomatOwnedSlice");
+
+ if let syn::Type::Path(p) = &ty {
+ if let Some(ident) = p.path.get_ident() {
+ let ident = ident.to_string();
+ let i = ident.as_str();
+ match i {
+ "DiplomatStrSlice" => {
+ return TypeName::StrSlice(
+ StringEncoding::UnvalidatedUtf8,
+ StdlibOrDiplomat::Diplomat,
+ )
+ }
+ "DiplomatStr16Slice" => {
+ return TypeName::StrSlice(
+ StringEncoding::UnvalidatedUtf16,
+ StdlibOrDiplomat::Diplomat,
+ )
+ }
+ "DiplomatUtf8StrSlice" => {
+ return TypeName::StrSlice(
+ StringEncoding::Utf8,
+ StdlibOrDiplomat::Diplomat,
+ )
+ }
+ _ => {
+ if let Ok(prim) = PrimitiveType::from_str(i) {
+ return TypeName::PrimitiveSlice(
+ ltmut,
+ prim,
+ StdlibOrDiplomat::Diplomat,
+ );
+ }
+ }
+ }
+ }
+ }
+ panic!("Found DiplomatSlice/DiplomatSliceMut/DiplomatOwnedSlice without primitive or DiplomatStrSlice-like generic");
+ } else if p_len == 1 && p.path.segments[0].ident == "Result"
+ || is_runtime_type(p, "DiplomatResult")
+ {
+ if let syn::PathArguments::AngleBracketed(type_args) =
+ &p.path.segments.last().unwrap().arguments
+ {
+ if let (syn::GenericArgument::Type(ok), syn::GenericArgument::Type(err)) =
+ (&type_args.args[0], &type_args.args[1])
+ {
+ let ok = TypeName::from_syn(ok, self_path_type.clone());
+ let err = TypeName::from_syn(err, self_path_type);
+ TypeName::Result(
+ Box::new(ok),
+ Box::new(err),
+ if is_runtime_type(p, "DiplomatResult") {
+ StdlibOrDiplomat::Diplomat
+ } else {
+ StdlibOrDiplomat::Stdlib
+ },
+ )
+ } else {
+ panic!("Expected both type arguments for Result to be a type")
+ }
+ } else {
+ panic!("Expected angle brackets for Result type")
+ }
+ } else if is_runtime_type(p, "DiplomatWrite") {
+ TypeName::Write
+ } else {
+ TypeName::Named(PathType::from(p))
+ }
+ }
+ syn::Type::Tuple(tup) => {
+ if tup.elems.is_empty() {
+ TypeName::Unit
+ } else {
+ todo!("Tuples are not currently supported")
+ }
+ }
+ syn::Type::ImplTrait(tr) => {
+ let trait_bound = tr.bounds.first();
+ if tr.bounds.len() > 1 {
+ todo!("Currently don't support implementing multiple traits");
+ }
+ if let Some(syn::TypeParamBound::Trait(syn::TraitBound { path: p, .. })) =
+ trait_bound
+ {
+ let rel_segs = &p.segments;
+ let path_seg = &rel_segs[0];
+ if path_seg.ident.eq("Fn") {
+ // we're in a function type
+ // get input and output args
+ if let syn::PathArguments::Parenthesized(
+ syn::ParenthesizedGenericArguments {
+ inputs: input_types,
+ output: output_type,
+ ..
+ },
+ ) = &path_seg.arguments
+ {
+ let in_types = input_types
+ .iter()
+ .map(|in_ty| {
+ Box::new(TypeName::from_syn(in_ty, self_path_type.clone()))
+ })
+ .collect::<Vec<Box<TypeName>>>();
+ let out_type = match output_type {
+ syn::ReturnType::Type(_, output_type) => {
+ TypeName::from_syn(output_type, self_path_type.clone())
+ }
+ syn::ReturnType::Default => TypeName::Unit,
+ };
+ let ret = TypeName::Function(in_types, Box::new(out_type));
+ return ret;
+ }
+ panic!("Unsupported function type: {:?}", &path_seg.arguments);
+ } else {
+ let ret = TypeName::ImplTrait(PathType::from(&syn::TraitBound {
+ paren_token: None,
+ modifier: syn::TraitBoundModifier::None,
+ lifetimes: None, // todo this is an assumption
+ path: p.clone(),
+ }));
+ return ret;
+ }
+ }
+ panic!("Unsupported trait type: {:?}", tr);
+ }
+ other => panic!("Unsupported type: {}", other.to_token_stream()),
+ }
+ }
+
+ /// Returns `true` if `self` is the `TypeName::SelfType` variant, otherwise
+ /// `false`.
+ pub fn is_self(&self) -> bool {
+ matches!(self, TypeName::SelfType(_))
+ }
+
+ /// Recurse down the type tree, visiting all lifetimes.
+ ///
+ /// Using this function, you can collect all the lifetimes into a collection,
+ /// or examine each one without having to make any additional allocations.
+ pub fn visit_lifetimes<'a, F, B>(&'a self, visit: &mut F) -> ControlFlow<B>
+ where
+ F: FnMut(&'a Lifetime, LifetimeOrigin) -> ControlFlow<B>,
+ {
+ match self {
+ TypeName::Named(path_type) | TypeName::SelfType(path_type) => path_type
+ .lifetimes
+ .iter()
+ .try_for_each(|lt| visit(lt, LifetimeOrigin::Named)),
+ TypeName::Reference(lt, _, ty) => {
+ ty.visit_lifetimes(visit)?;
+ visit(lt, LifetimeOrigin::Reference)
+ }
+ TypeName::Box(ty) | TypeName::Option(ty, _) => ty.visit_lifetimes(visit),
+ TypeName::Result(ok, err, _) => {
+ ok.visit_lifetimes(visit)?;
+ err.visit_lifetimes(visit)
+ }
+ TypeName::StrReference(Some(lt), ..) => visit(lt, LifetimeOrigin::StrReference),
+ TypeName::PrimitiveSlice(Some((lt, _)), ..) => {
+ visit(lt, LifetimeOrigin::PrimitiveSlice)
+ }
+ _ => ControlFlow::Continue(()),
+ }
+ }
+
+ /// Returns `true` if any lifetime satisfies a predicate, otherwise `false`.
+ ///
+ /// This method is short-circuiting, meaning that if the predicate ever succeeds,
+ /// it will return immediately.
+ pub fn any_lifetime<'a, F>(&'a self, mut f: F) -> bool
+ where
+ F: FnMut(&'a Lifetime, LifetimeOrigin) -> bool,
+ {
+ self.visit_lifetimes(&mut |lifetime, origin| {
+ if f(lifetime, origin) {
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(())
+ }
+ })
+ .is_break()
+ }
+
+ /// Returns `true` if all lifetimes satisfy a predicate, otherwise `false`.
+ ///
+ /// This method is short-circuiting, meaning that if the predicate ever fails,
+ /// it will return immediately.
+ pub fn all_lifetimes<'a, F>(&'a self, mut f: F) -> bool
+ where
+ F: FnMut(&'a Lifetime, LifetimeOrigin) -> bool,
+ {
+ self.visit_lifetimes(&mut |lifetime, origin| {
+ if f(lifetime, origin) {
+ ControlFlow::Continue(())
+ } else {
+ ControlFlow::Break(())
+ }
+ })
+ .is_continue()
+ }
+
+ /// Returns all lifetimes in a [`LifetimeEnv`] that must live at least as
+ /// long as the type.
+ pub fn longer_lifetimes<'env>(
+ &self,
+ lifetime_env: &'env LifetimeEnv,
+ ) -> Vec<&'env NamedLifetime> {
+ self.transitive_lifetime_bounds(LifetimeTransitivity::longer(lifetime_env))
+ }
+
+ /// Returns all lifetimes in a [`LifetimeEnv`] that are outlived by the type.
+ pub fn shorter_lifetimes<'env>(
+ &self,
+ lifetime_env: &'env LifetimeEnv,
+ ) -> Vec<&'env NamedLifetime> {
+ self.transitive_lifetime_bounds(LifetimeTransitivity::shorter(lifetime_env))
+ }
+
+ /// Visits the provided [`LifetimeTransitivity`] value with all `NamedLifetime`s
+ /// in the type tree, and returns the transitively reachable lifetimes.
+ fn transitive_lifetime_bounds<'env>(
+ &self,
+ mut transitivity: LifetimeTransitivity<'env>,
+ ) -> Vec<&'env NamedLifetime> {
+ self.visit_lifetimes(&mut |lifetime, _| -> ControlFlow<()> {
+ if let Lifetime::Named(named) = lifetime {
+ transitivity.visit(named);
+ }
+ ControlFlow::Continue(())
+ });
+ transitivity.finish()
+ }
+
+ pub fn is_zst(&self) -> bool {
+ // check_zst() prevents non-unit types from being ZSTs
+ matches!(*self, TypeName::Unit)
+ }
+
+ pub fn is_pointer(&self) -> bool {
+ matches!(*self, TypeName::Reference(..) | TypeName::Box(_))
+ }
+}
+
+#[non_exhaustive]
+pub enum LifetimeOrigin {
+ Named,
+ Reference,
+ StrReference,
+ PrimitiveSlice,
+}
+
+fn is_runtime_type(p: &syn::TypePath, name: &str) -> bool {
+ (p.path.segments.len() == 1 && p.path.segments[0].ident == name)
+ || (p.path.segments.len() == 2
+ && p.path.segments[0].ident == "diplomat_runtime"
+ && p.path.segments[1].ident == name)
+}
+
+impl fmt::Display for TypeName {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ TypeName::Primitive(p) => p.fmt(f),
+ TypeName::Ordering => write!(f, "Ordering"),
+ TypeName::Named(p) | TypeName::SelfType(p) => p.fmt(f),
+ TypeName::Reference(lifetime, mutability, typ) => {
+ write!(f, "{}{typ}", ReferenceDisplay(lifetime, mutability))
+ }
+ TypeName::Box(typ) => write!(f, "Box<{typ}>"),
+ TypeName::Option(typ, StdlibOrDiplomat::Stdlib) => write!(f, "Option<{typ}>"),
+ TypeName::Option(typ, StdlibOrDiplomat::Diplomat) => write!(f, "DiplomatOption<{typ}>"),
+ TypeName::Result(ok, err, _) => {
+ write!(f, "Result<{ok}, {err}>")
+ }
+ TypeName::Write => "DiplomatWrite".fmt(f),
+ TypeName::StrReference(lt, encoding, is_stdlib_type) => {
+ if let Some(lt) = lt {
+ if *is_stdlib_type == StdlibOrDiplomat::Stdlib {
+ let lt = ReferenceDisplay(lt, &Mutability::Immutable);
+ let ty = encoding.get_diplomat_slice_type_str();
+ write!(f, "{lt}{ty}")
+ } else {
+ let ty = encoding.get_stdlib_slice_type_str();
+ let lt = LifetimeGenericsListDisplay(lt);
+ write!(f, "{ty}{lt}")
+ }
+ } else {
+ match (encoding, is_stdlib_type) {
+ (_, StdlibOrDiplomat::Stdlib) => {
+ write!(f, "Box<{}>", encoding.get_diplomat_slice_type_str())
+ }
+ (StringEncoding::Utf8, StdlibOrDiplomat::Diplomat) => {
+ "DiplomatOwnedUtf8Str".fmt(f)
+ }
+ (StringEncoding::UnvalidatedUtf8, StdlibOrDiplomat::Diplomat) => {
+ "DiplomatOwnedStrSlice".fmt(f)
+ }
+ (StringEncoding::UnvalidatedUtf16, StdlibOrDiplomat::Diplomat) => {
+ "DiplomatOwnedStr16Slice".fmt(f)
+ }
+ }
+ }
+ }
+
+ TypeName::StrSlice(encoding, StdlibOrDiplomat::Stdlib) => {
+ let inner = encoding.get_stdlib_slice_type_str();
+
+ write!(f, "&[&{inner}]")
+ }
+ TypeName::StrSlice(encoding, StdlibOrDiplomat::Diplomat) => {
+ let inner = encoding.get_diplomat_slice_type_str();
+ write!(f, "DiplomatSlice<{inner}>")
+ }
+
+ TypeName::PrimitiveSlice(
+ Some((lifetime, mutability)),
+ typ,
+ StdlibOrDiplomat::Stdlib,
+ ) => {
+ write!(f, "{}[{typ}]", ReferenceDisplay(lifetime, mutability))
+ }
+ TypeName::PrimitiveSlice(
+ Some((lifetime, mutability)),
+ typ,
+ StdlibOrDiplomat::Diplomat,
+ ) => {
+ let maybemut = if *mutability == Mutability::Immutable {
+ ""
+ } else {
+ "Mut"
+ };
+ let lt = LifetimeGenericsListPartialDisplay(lifetime);
+ write!(f, "DiplomatSlice{maybemut}<{lt}{typ}>")
+ }
+ TypeName::PrimitiveSlice(None, typ, _) => write!(f, "Box<[{typ}]>"),
+ TypeName::Unit => "()".fmt(f),
+ TypeName::Function(input_types, out_type) => {
+ write!(f, "fn (")?;
+ for in_typ in input_types.iter() {
+ write!(f, "{in_typ}")?;
+ }
+ write!(f, ")->{out_type}")
+ }
+ TypeName::ImplTrait(trt) => {
+ write!(f, "impl ")?;
+ trt.fmt(f)
+ }
+ }
+ }
+}
+
+/// An [`fmt::Display`] type for formatting Rust references.
+///
+/// # Examples
+///
+/// ```ignore
+/// let lifetime = Lifetime::from(&syn::parse_str::<syn::Lifetime>("'a"));
+/// let mutability = Mutability::Mutable;
+/// // ...
+/// let fmt = format!("{}[u8]", ReferenceDisplay(&lifetime, &mutability));
+///
+/// assert_eq!(fmt, "&'a mut [u8]");
+/// ```
+struct ReferenceDisplay<'a>(&'a Lifetime, &'a Mutability);
+
+impl<'a> fmt::Display for ReferenceDisplay<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ Lifetime::Static => "&'static ".fmt(f)?,
+ Lifetime::Named(lifetime) => write!(f, "&{lifetime} ")?,
+ Lifetime::Anonymous => '&'.fmt(f)?,
+ }
+
+ if self.1.is_mutable() {
+ "mut ".fmt(f)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl<'a> quote::ToTokens for ReferenceDisplay<'a> {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ let lifetime = self.0.to_syn();
+ let mutability = self.1.to_syn();
+
+ tokens.append_all(quote::quote!(& #lifetime #mutability))
+ }
+}
+
+/// An [`fmt::Display`] type for formatting Rust lifetimes as they show up in generics list, when
+/// the generics list has no other elements
+struct LifetimeGenericsListDisplay<'a>(&'a Lifetime);
+
+impl<'a> fmt::Display for LifetimeGenericsListDisplay<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ Lifetime::Static => "<'static>".fmt(f),
+ Lifetime::Named(lifetime) => write!(f, "<{lifetime}>"),
+ Lifetime::Anonymous => Ok(()),
+ }
+ }
+}
+
+impl<'a> quote::ToTokens for LifetimeGenericsListDisplay<'a> {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ if let Lifetime::Anonymous = self.0 {
+ } else {
+ let lifetime = self.0.to_syn();
+ tokens.append_all(quote::quote!(<#lifetime>))
+ }
+ }
+}
+
+/// An [`fmt::Display`] type for formatting Rust lifetimes as they show up in generics list, when
+/// the generics list has another element
+struct LifetimeGenericsListPartialDisplay<'a>(&'a Lifetime);
+
+impl<'a> fmt::Display for LifetimeGenericsListPartialDisplay<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ Lifetime::Static => "'static,".fmt(f),
+ Lifetime::Named(lifetime) => write!(f, "{lifetime},"),
+ Lifetime::Anonymous => Ok(()),
+ }
+ }
+}
+
+impl<'a> quote::ToTokens for LifetimeGenericsListPartialDisplay<'a> {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ if let Lifetime::Anonymous = self.0 {
+ } else {
+ let lifetime = self.0.to_syn();
+ tokens.append_all(quote::quote!(#lifetime,))
+ }
+ }
+}
+
+impl fmt::Display for PathType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.path.fmt(f)?;
+
+ if let Some((first, rest)) = self.lifetimes.split_first() {
+ write!(f, "<{first}")?;
+ for lifetime in rest {
+ write!(f, ", {lifetime}")?;
+ }
+ '>'.fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+/// A built-in Rust primitive scalar type.
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
+#[allow(non_camel_case_types)]
+#[allow(clippy::exhaustive_enums)] // there are only these (scalar types)
+pub enum PrimitiveType {
+ i8,
+ u8,
+ i16,
+ u16,
+ i32,
+ u32,
+ i64,
+ u64,
+ i128,
+ u128,
+ isize,
+ usize,
+ f32,
+ f64,
+ bool,
+ char,
+ /// a primitive byte that is not meant to be interpreted numerically
+ /// in languages that don't have fine-grained integer types
+ byte,
+}
+
+impl PrimitiveType {
+ fn as_code_str(self) -> &'static str {
+ match self {
+ PrimitiveType::i8 => "i8",
+ PrimitiveType::u8 => "u8",
+ PrimitiveType::i16 => "i16",
+ PrimitiveType::u16 => "u16",
+ PrimitiveType::i32 => "i32",
+ PrimitiveType::u32 => "u32",
+ PrimitiveType::i64 => "i64",
+ PrimitiveType::u64 => "u64",
+ PrimitiveType::i128 => "i128",
+ PrimitiveType::u128 => "u128",
+ PrimitiveType::isize => "isize",
+ PrimitiveType::usize => "usize",
+ PrimitiveType::f32 => "f32",
+ PrimitiveType::f64 => "f64",
+ PrimitiveType::bool => "bool",
+ PrimitiveType::char => "DiplomatChar",
+ PrimitiveType::byte => "DiplomatByte",
+ }
+ }
+
+ fn to_ident(self) -> proc_macro2::Ident {
+ proc_macro2::Ident::new(self.as_code_str(), Span::call_site())
+ }
+
+ /// Get the type for a slice of this, as specified using Rust stdlib types
+ pub fn get_stdlib_slice_type(self, lt: &Option<(Lifetime, Mutability)>) -> syn::Type {
+ let primitive = self.to_ident();
+
+ if let Some((ref lt, ref mtbl)) = lt {
+ let reference = ReferenceDisplay(lt, mtbl);
+ syn::parse_quote_spanned!(Span::call_site() => #reference [#primitive])
+ } else {
+ syn::parse_quote_spanned!(Span::call_site() => Box<[#primitive]>)
+ }
+ }
+
+ /// Get the type for a slice of this, as specified using Diplomat runtime types
+ pub fn get_diplomat_slice_type(self, lt: &Option<(Lifetime, Mutability)>) -> syn::Type {
+ let primitive = self.to_ident();
+
+ if let Some((lt, mtbl)) = lt {
+ let lifetime = LifetimeGenericsListPartialDisplay(lt);
+
+ if *mtbl == Mutability::Immutable {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatSlice<#lifetime #primitive>)
+ } else {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatSliceMut<#lifetime #primitive>)
+ }
+ } else {
+ syn::parse_quote_spanned!(Span::call_site() => diplomat_runtime::DiplomatOwnedSlice<#primitive>)
+ }
+ }
+}
+
+impl fmt::Display for PrimitiveType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ PrimitiveType::byte => "u8",
+ PrimitiveType::char => "char",
+ _ => self.as_code_str(),
+ }
+ .fmt(f)
+ }
+}
+
+impl FromStr for PrimitiveType {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, ()> {
+ Ok(match s {
+ "i8" => PrimitiveType::i8,
+ "u8" => PrimitiveType::u8,
+ "i16" => PrimitiveType::i16,
+ "u16" => PrimitiveType::u16,
+ "i32" => PrimitiveType::i32,
+ "u32" => PrimitiveType::u32,
+ "i64" => PrimitiveType::i64,
+ "u64" => PrimitiveType::u64,
+ "i128" => PrimitiveType::i128,
+ "u128" => PrimitiveType::u128,
+ "isize" => PrimitiveType::isize,
+ "usize" => PrimitiveType::usize,
+ "f32" => PrimitiveType::f32,
+ "f64" => PrimitiveType::f64,
+ "bool" => PrimitiveType::bool,
+ "DiplomatChar" => PrimitiveType::char,
+ "DiplomatByte" => PrimitiveType::byte,
+ _ => return Err(()),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use insta;
+
+ use syn;
+
+ use super::TypeName;
+
+ #[test]
+ fn typename_primitives() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ i32
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ usize
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ bool
+ },
+ None
+ ));
+ }
+
+ #[test]
+ fn typename_named() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ MyLocalStruct
+ },
+ None
+ ));
+ }
+
+ #[test]
+ fn typename_references() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ &i32
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ &mut MyLocalStruct
+ },
+ None
+ ));
+ }
+
+ #[test]
+ fn typename_boxes() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Box<i32>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Box<MyLocalStruct>
+ },
+ None
+ ));
+ }
+
+ #[test]
+ fn typename_option() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Option<i32>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Option<MyLocalStruct>
+ },
+ None
+ ));
+ }
+
+ #[test]
+ fn typename_result() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ DiplomatResult<MyLocalStruct, i32>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ DiplomatResult<(), MyLocalStruct>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Result<MyLocalStruct, i32>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Result<(), MyLocalStruct>
+ },
+ None
+ ));
+ }
+
+ #[test]
+ fn lifetimes() {
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Foo<'a, 'b>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ ::core::my_type::Foo
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ ::core::my_type::Foo<'test>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Option<Ref<'object>>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Foo<'a, 'b, 'c, 'd>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ very::long::path::to::my::Type<'x, 'y, 'z>
+ },
+ None
+ ));
+
+ insta::assert_yaml_snapshot!(TypeName::from_syn(
+ &syn::parse_quote! {
+ Result<OkRef<'a, 'b>, ErrRef<'c>>
+ },
+ None
+ ));
+ }
+}
diff --git a/crates/diplomat_core/src/environment.rs b/crates/diplomat_core/src/environment.rs
new file mode 100644
index 0000000..67b0f96
--- /dev/null
+++ b/crates/diplomat_core/src/environment.rs
@@ -0,0 +1,96 @@
+use crate::ast::*;
+use std::collections::BTreeMap;
+use std::ops::Index;
+
+/// The type resolution environment
+///
+/// Also contains the entire module structure
+#[derive(Default, Clone)]
+pub struct Env {
+ pub(crate) env: BTreeMap<Path, ModuleEnv>,
+}
+
+/// The type resolution environment within a specific module
+#[derive(Clone)]
+pub struct ModuleEnv {
+ pub(crate) module: BTreeMap<Ident, ModSymbol>,
+ #[cfg_attr(not(feature = "hir"), allow(unused))]
+ pub(crate) attrs: Attrs,
+}
+
+impl Env {
+ pub(crate) fn insert(&mut self, path: Path, module: ModuleEnv) {
+ self.env.insert(path, module);
+ }
+
+ /// Given a path to a module and a name, get the item, if any
+ pub fn get(&self, path: &Path, name: &str) -> Option<&ModSymbol> {
+ self.env.get(path).and_then(|m| m.module.get(name))
+ }
+
+ /// Iterate over all items in the environment
+ ///
+ /// This will occur in a stable lexically sorted order by path and then name
+ pub fn iter_items(&self) -> impl Iterator<Item = (&Path, &Ident, &ModSymbol)> + '_ {
+ self.env
+ .iter()
+ .flat_map(|(k, v)| v.module.iter().map(move |v2| (k, v2.0, v2.1)))
+ }
+
+ /// Iterate over all modules
+ ///
+ /// This will occur in a stable lexically sorted order by path
+ pub fn iter_modules(&self) -> impl Iterator<Item = (&Path, &ModuleEnv)> + '_ {
+ self.env.iter()
+ }
+}
+
+impl ModuleEnv {
+ pub(crate) fn new(attrs: Attrs) -> Self {
+ Self {
+ module: Default::default(),
+ attrs,
+ }
+ }
+ pub(crate) fn insert(&mut self, name: Ident, symbol: ModSymbol) -> Option<ModSymbol> {
+ self.module.insert(name, symbol)
+ }
+
+ /// Given an item name, fetch it
+ pub fn get(&self, name: &str) -> Option<&ModSymbol> {
+ self.module.get(name)
+ }
+
+ /// Iterate over all name-item pairs in this module
+ pub fn iter(&self) -> impl Iterator<Item = (&Ident, &ModSymbol)> + '_ {
+ self.module.iter()
+ }
+
+ /// Iterate over all names in this module
+ ///
+ /// This will occur in a stable lexically sorted order by name
+ pub fn names(&self) -> impl Iterator<Item = &Ident> + '_ {
+ self.module.keys()
+ }
+
+ /// Iterate over all items in this module
+ ///
+ /// This will occur in a stable lexically sorted order by name
+ pub fn items(&self) -> impl Iterator<Item = &ModSymbol> + '_ {
+ self.module.values()
+ }
+}
+
+impl Index<&Path> for Env {
+ type Output = ModuleEnv;
+ fn index(&self, i: &Path) -> &ModuleEnv {
+ &self.env[i]
+ }
+}
+
+impl Index<&str> for ModuleEnv {
+ type Output = ModSymbol;
+ fn index(&self, i: &str) -> &ModSymbol {
+ &self.module[i]
+ }
+}
diff --git a/crates/diplomat_core/src/hir/attrs.rs b/crates/diplomat_core/src/hir/attrs.rs
new file mode 100644
index 0000000..c7c2261
--- /dev/null
+++ b/crates/diplomat_core/src/hir/attrs.rs
@@ -0,0 +1,1270 @@
+//! #[diplomat::attr] and other attributes
+
+use crate::ast;
+use crate::ast::attrs::{AttrInheritContext, DiplomatBackendAttrCfg, StandardAttribute};
+use crate::hir::lowering::ErrorStore;
+use crate::hir::{
+ EnumVariant, LoweringError, Method, Mutability, OpaqueId, ReturnType, SelfType, SuccessType,
+ TraitDef, Type, TypeDef, TypeId,
+};
+use syn::Meta;
+
+pub use crate::ast::attrs::RenameAttr;
+
+/// Diplomat attribute that can be specified on items, methods, and enum variants. These
+/// can be used to control the codegen in a particular backend.
+///
+/// Most of these are specified via `#[diplomat::attr(some cfg here, attrname)]`, where `some cfg here`
+/// can be used to pick which backends something applies to.
+#[non_exhaustive]
+#[derive(Clone, Default, Debug)]
+pub struct Attrs {
+ /// "disable" this item: do not generate code for it in the backend
+ ///
+ /// This attribute is always inherited except to variants
+ pub disable: bool,
+ /// An optional namespace. None is equivalent to the root namespace.
+ ///
+ /// This attribute is inherited to types (and is not allowed elsewhere)
+ pub namespace: Option<String>,
+ /// Rename this item/method/variant
+ ///
+ /// This attribute is inherited except through methods and variants (and is not allowed on variants)
+ pub rename: RenameAttr,
+ /// Rename this item in the C ABI. This *must* be respected by backends.
+ ///
+ /// This attribute is inherited except through variants
+ pub abi_rename: RenameAttr,
+ /// This method is "special": it should generate something other than a regular method on the other side.
+ /// This can be something like a constructor, an accessor, a stringifier etc.
+ ///
+ /// This attribute does not participate in inheritance and must always
+ /// be specified on individual methods
+ pub special_method: Option<SpecialMethod>,
+
+ /// From #[diplomat::demo()]. Created from [`crate::ast::attrs::Attrs::demo_attrs`].
+ /// List of attributes specific to automatic demo generation.
+ /// Currently just for demo_gen in diplomat-tool (which generates sample webpages), but could be used for broader purposes (i.e., demo Android apps)
+ pub demo_attrs: DemoInfo,
+}
+
+// #region: Demo specific attributes.
+
+/// For `#[diplomat::demo(input(...))]`, stored in [DemoInfo::input_cfg].
+#[non_exhaustive]
+#[derive(Clone, Default, Debug)]
+pub struct DemoInputCFG {
+ /// `#[diplomat::demo(input(label = "..."))]`
+ /// Label that this input parameter should have. Let demo_gen pick a valid name if this is empty.
+ ///
+ /// For instance <label for="v">Number Here</label><input name="v"/>
+ pub label: String,
+
+ /// `#[diplomat::demo(input(default_value = "..."))]`
+ /// Sets the default value for a parameter.
+ ///
+ /// Should ALWAYS be a string. The HTML renderer is expected to do validation for us.
+ pub default_value: String,
+}
+
+#[non_exhaustive]
+#[derive(Clone, Default, Debug)]
+pub struct DemoInfo {
+ /// `#[diplomat::demo(generate)]`. If automatic generation is disabled by default (see [`diplomat_tool::demo_gen::DemoConfig`]), then the below render terminus will be allowed to generate.
+ pub generate: bool,
+
+ /// `#[diplomat::demo(default_constructor)]`
+ /// We search for any methods specially tagged with `Constructor`, but if there's are no default Constructors and there's NamedConstructor that you want to be default instead, use this.
+ /// TODO: Should probably ignore other `Constructors` if a default has been set.
+ pub default_constructor: bool,
+
+ /// `#[diplomat::demo(external)]` represents an item that we will not evaluate, and should be passed to the rendering engine to provide.
+ pub external: bool,
+
+ /// `#[diplomat::demo(custom_func = "/file/name/here.mjs")]` can be used above any `struct` definition in the bridge. The linked `.mjs` should contain a JS definition of functions that should be bundled with demo_gen's output.
+ ///
+ /// We call these functions "custom functions", as they are JS functions that are not automagically generated by demo_gen, but rather included as part of its JS output in the `RenderInfo` object.
+ ///
+ /// For more information on custom functions (and their use), see the relevant chapter in [the book](https://rust-diplomat.github.io/book/demo_gen/custom_functions.html).
+ ///
+ /// Files are located relative to lib.rs.
+ ///
+ pub custom_func: Option<String>,
+
+ /// `#[diplomat::demo(input(...))]` represents configuration options for anywhere we might expect user input.
+ pub input_cfg: DemoInputCFG,
+}
+
+// #endregion
+
+/// Attributes that mark methods as "special"
+#[non_exhaustive]
+#[derive(Clone, Debug)]
+pub enum SpecialMethod {
+ /// A constructor.
+ ///
+ /// Must return Self (or Result<Self> for backends with `fallible_constructors` enabled )
+ Constructor,
+ /// A named constructor, with optional name. If the name isn't specified, it will be derived
+ /// from the method name
+ ///
+ /// Must return Self (or Result<Self> for backends with `fallible_constructors` enabled )
+ NamedConstructor(Option<String>),
+
+ /// A getter, with optional name. If the name isn't specified, it will be derived
+ /// from the method name
+ ///
+ /// Must have no parameters and must return something.
+ Getter(Option<String>),
+ /// A setter, with optional name. If the name isn't specified, it will be derived
+ /// from the method name
+ ///
+ /// Must have no return type (aside from potentially a `Result<(), _>`) and must have one parameter
+ Setter(Option<String>),
+ /// A stringifier. Must have no parameters and return a string (DiplomatWrite)
+ Stringifier,
+ /// A comparison operator. Currently unsupported
+ Comparison,
+ /// An iterator (a type that is mutated to produce new values)
+ Iterator,
+ /// An iterable (a type that can produce an iterator)
+ Iterable,
+ /// Indexes into the type using an integer
+ Indexer,
+}
+
+/// For special methods that affect type semantics, whether this type has this method.
+///
+/// This will likely only contain a subset of special methods, but feel free to add more as needed.
+#[derive(Debug, Default)]
+#[non_exhaustive]
+pub struct SpecialMethodPresence {
+ pub comparator: bool,
+ /// If it is an iterator, the type it iterates over
+ pub iterator: Option<SuccessType>,
+ /// If it is an iterable, the iterator type it returns (*not* the type it iterates over,
+ /// perform lookup on that type to access)
+ pub iterable: Option<OpaqueId>,
+}
+
+/// Where the attribute was found. Some attributes are only allowed in some contexts
+/// (e.g. namespaces cannot be specified on methods)
+#[non_exhaustive] // might add module attrs in the future
+#[derive(Debug)]
+pub enum AttributeContext<'a, 'b> {
+ Type(TypeDef<'a>),
+ Trait(&'a TraitDef),
+ EnumVariant(&'a EnumVariant),
+ Method(&'a Method, TypeId, &'b mut SpecialMethodPresence),
+ Module,
+ Param,
+ SelfParam,
+ Field,
+}
+
+fn maybe_error_unsupported(
+ auto_found: bool,
+ attribute: &str,
+ backend: &str,
+ errors: &mut ErrorStore,
+) {
+ if !auto_found {
+ errors.push(LoweringError::Other(format!(
+ "`{attribute}` not supported in backend {backend}"
+ )));
+ }
+}
+impl Attrs {
+ pub fn from_ast(
+ ast: &ast::Attrs,
+ validator: &(impl AttributeValidator + ?Sized),
+ parent_attrs: &Attrs,
+ errors: &mut ErrorStore,
+ ) -> Self {
+ let mut this = parent_attrs.clone();
+ // Backends must support this since it applies to the macro/C code.
+ // No special inheritance, was already appropriately inherited in AST
+ this.abi_rename = ast.abi_rename.clone();
+
+ let support = validator.attrs_supported();
+ let backend = validator.primary_name();
+ for attr in &ast.attrs {
+ let mut auto_found = false;
+ let mut auto_used = false;
+ let satisfies = match validator.satisfies_cfg(&attr.cfg, Some(&mut auto_found)) {
+ Ok(satisfies) => satisfies,
+ Err(e) => {
+ errors.push(e);
+ continue;
+ }
+ };
+ if satisfies {
+ let path = attr.meta.path();
+ if let Some(path) = path.get_ident() {
+ if path == "disable" {
+ if let Meta::Path(_) = attr.meta {
+ if this.disable {
+ errors.push(LoweringError::Other(
+ "Duplicate `disable` attribute".into(),
+ ));
+ } else {
+ this.disable = true;
+ }
+ } else {
+ errors.push(LoweringError::Other(
+ "`disable` must be a simple path".into(),
+ ))
+ }
+ } else if path == "rename" {
+ match RenameAttr::from_meta(&attr.meta) {
+ Ok(rename) => {
+ // We use the override extend mode: a single ast::Attrs
+ // will have had these attributes inherited into the list by appending
+ // to the end; so a later attribute in the list is more pertinent.
+ this.rename.extend(&rename);
+ }
+ Err(e) => errors.push(LoweringError::Other(format!(
+ "`rename` attr failed to parse: {e:?}"
+ ))),
+ }
+ } else if path == "namespace" {
+ if !support.namespacing {
+ maybe_error_unsupported(auto_found, "constructor", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ match StandardAttribute::from_meta(&attr.meta) {
+ Ok(StandardAttribute::String(s)) if s.is_empty() => {
+ this.namespace = None
+ }
+ Ok(StandardAttribute::String(s)) => this.namespace = Some(s),
+ Ok(_) | Err(_) => {
+ errors.push(LoweringError::Other(
+ "`namespace` must have a single string parameter".to_string(),
+ ));
+ continue;
+ }
+ }
+ } else if path == "constructor"
+ || path == "stringifier"
+ || path == "comparison"
+ || path == "iterable"
+ || path == "iterator"
+ || path == "indexer"
+ {
+ if let Some(ref existing) = this.special_method {
+ errors.push(LoweringError::Other(format!(
+ "Multiple special method markers found on the same method, found {path} and {existing:?}"
+ )));
+ continue;
+ }
+ let kind = if path == "constructor" {
+ if !support.constructors {
+ maybe_error_unsupported(auto_found, "constructor", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Constructor
+ } else if path == "stringifier" {
+ if !support.stringifiers {
+ maybe_error_unsupported(auto_found, "stringifier", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Stringifier
+ } else if path == "iterable" {
+ if !support.iterables {
+ maybe_error_unsupported(auto_found, "iterable", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Iterable
+ } else if path == "iterator" {
+ if !support.iterators {
+ maybe_error_unsupported(auto_found, "iterator", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Iterator
+ } else if path == "indexer" {
+ if !support.indexing {
+ maybe_error_unsupported(auto_found, "indexer", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Indexer
+ } else {
+ if !support.comparators {
+ maybe_error_unsupported(auto_found, "comparator", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Comparison
+ };
+
+ this.special_method = Some(kind);
+ } else if path == "named_constructor" || path == "getter" || path == "setter" {
+ if let Some(ref existing) = this.special_method {
+ errors.push(LoweringError::Other(format!(
+ "Multiple special method markers found on the same method, found {path} and {existing:?}"
+ )));
+ continue;
+ }
+ let kind = if path == "named_constructor" {
+ if !support.named_constructors {
+ maybe_error_unsupported(
+ auto_found,
+ "named_constructors",
+ backend,
+ errors,
+ );
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::NamedConstructor
+ } else if path == "getter" {
+ if !support.accessors {
+ maybe_error_unsupported(auto_found, "accessors", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Getter
+ } else {
+ if !support.accessors {
+ maybe_error_unsupported(auto_found, "accessors", backend, errors);
+ continue;
+ }
+ auto_used = true;
+ SpecialMethod::Setter
+ };
+ match StandardAttribute::from_meta(&attr.meta) {
+ Ok(StandardAttribute::String(s)) => {
+ this.special_method = Some(kind(Some(s)))
+ }
+ Ok(StandardAttribute::Empty) => this.special_method = Some(kind(None)),
+ Ok(_) | Err(_) => {
+ errors.push(LoweringError::Other(format!(
+ "`{path}` must have a single string parameter or no parameter",
+ )));
+ continue;
+ }
+ }
+ } else {
+ errors.push(LoweringError::Other(format!(
+ "Unknown diplomat attribute {path}: expected one of: `disable, rename, namespace, constructor, stringifier, comparison, named_constructor, getter, setter, indexer`"
+ )));
+ }
+ if auto_found && !auto_used {
+ errors.push(LoweringError::Other(format!(
+ "Diplomat attribute {path} gated on 'auto' but is not one that works with 'auto'"
+ )));
+ }
+ } else {
+ errors.push(LoweringError::Other(format!(
+ "Unknown diplomat attribute {path:?}: expected one of: `disable, rename, namespace, constructor, stringifier, comparison, named_constructor, getter, setter, indexer`"
+ )));
+ }
+ }
+ }
+
+ for attr in &ast.demo_attrs {
+ let path = attr.meta.path();
+ if let Some(path_ident) = path.get_ident() {
+ if path_ident == "external" {
+ this.demo_attrs.external = true;
+ } else if path_ident == "default_constructor" {
+ this.demo_attrs.default_constructor = true;
+ } else if path_ident == "generate" {
+ this.demo_attrs.generate = true;
+ } else if path_ident == "input" {
+ let meta_list = attr
+ .meta
+ .require_list()
+ .expect("Could not get MetaList, expected #[diplomat::demo(input(...))]");
+
+ meta_list
+ .parse_nested_meta(|meta| {
+ if meta.path.is_ident("label") {
+ let value = meta.value()?;
+ let s: syn::LitStr = value.parse()?;
+ this.demo_attrs.input_cfg.label = s.value();
+ Ok(())
+ } else if meta.path.is_ident("default_value") {
+ let value = meta.value()?;
+
+ let str_val: String;
+
+ let ahead = value.lookahead1();
+ if ahead.peek(syn::LitFloat) {
+ let s: syn::LitFloat = value.parse()?;
+ str_val = s.base10_parse::<f64>()?.to_string();
+ } else if ahead.peek(syn::LitInt) {
+ let s: syn::LitInt = value.parse()?;
+ str_val = s.base10_parse::<i64>()?.to_string();
+ } else {
+ let s: syn::LitStr = value.parse()?;
+ str_val = s.value();
+ }
+ this.demo_attrs.input_cfg.default_value = str_val;
+ Ok(())
+ } else {
+ Err(meta.error(format!(
+ "Unsupported ident {:?}",
+ meta.path.get_ident()
+ )))
+ }
+ })
+ .expect("Could not read input(...)");
+ } else if path_ident == "custom_func" {
+ let v = &attr.meta.require_name_value().unwrap().value;
+
+ if let syn::Expr::Lit(s) = v {
+ if let syn::Lit::Str(string) = &s.lit {
+ this.demo_attrs.custom_func = Some(string.value());
+ } else {
+ errors.push(LoweringError::Other(format!(
+ "#[diplomat::demo(custom_func={s:?}) must be a literal string."
+ )));
+ }
+ } else {
+ errors.push(LoweringError::Other(format!(
+ "#[diplomat::demo(custom_func={v:?}) must be a literal string."
+ )));
+ }
+ } else {
+ errors.push(LoweringError::Other(format!(
+ "Unknown demo_attr: {path_ident:?}"
+ )));
+ }
+ } else {
+ errors.push(LoweringError::Other(format!("Unknown demo_attr: {path:?}")));
+ }
+ }
+
+ this
+ }
+
+ /// Validate that this attribute is allowed in this context
+ pub(crate) fn validate(
+ &self,
+ validator: &(impl AttributeValidator + ?Sized),
+ mut context: AttributeContext,
+ errors: &mut ErrorStore,
+ ) {
+ // use an exhaustive destructure so new attributes are handled
+ let Attrs {
+ disable,
+ namespace,
+ rename,
+ abi_rename,
+ special_method,
+ demo_attrs: _,
+ } = &self;
+
+ if *disable && matches!(context, AttributeContext::EnumVariant(..)) {
+ errors.push(LoweringError::Other(
+ "`disable` cannot be used on enum variants".into(),
+ ))
+ }
+
+ if let Some(ref special) = special_method {
+ if let AttributeContext::Method(method, self_id, ref mut special_method_presence) =
+ context
+ {
+ match special {
+ SpecialMethod::Constructor | SpecialMethod::NamedConstructor(..) => {
+ if method.param_self.is_some() {
+ errors.push(LoweringError::Other(
+ "Constructors must not accept a self parameter".to_string(),
+ ))
+ }
+ let output = method.output.success_type();
+ match method.output {
+ ReturnType::Infallible(_) => (),
+ ReturnType::Fallible(..) => {
+ if !validator.attrs_supported().fallible_constructors {
+ errors.push(LoweringError::Other(
+ "This backend doesn't support fallible constructors"
+ .to_string(),
+ ))
+ }
+ }
+ ReturnType::Nullable(..) => {
+ errors.push(LoweringError::Other("Diplomat doesn't support turning nullable methods into constructors".to_string()));
+ }
+ }
+
+ if let SuccessType::OutType(t) = &output {
+ if t.id() != Some(self_id) {
+ errors.push(LoweringError::Other(
+ "Constructors must return Self!".to_string(),
+ ));
+ }
+ } else {
+ errors.push(LoweringError::Other(
+ "Constructors must return Self!".to_string(),
+ ));
+ }
+ }
+ SpecialMethod::Getter(_) => {
+ if !method.params.is_empty() {
+ errors
+ .push(LoweringError::Other("Getter cannot have parameters".into()));
+ }
+
+ // Currently does not forbid nullable getters, could if desired
+ }
+
+ SpecialMethod::Setter(_) => {
+ if !matches!(method.output.success_type(), SuccessType::Unit) {
+ errors.push(LoweringError::Other("Setters must return unit".into()));
+ }
+ if method.params.len() != 1 {
+ errors.push(LoweringError::Other(
+ "Setter must have exactly one parameter".into(),
+ ))
+ }
+
+ // Currently does not forbid fallible setters, could if desired
+ }
+ SpecialMethod::Stringifier => {
+ if !method.params.is_empty() {
+ errors
+ .push(LoweringError::Other("Getter cannot have parameters".into()));
+ }
+ if !matches!(method.output.success_type(), SuccessType::Write) {
+ errors.push(LoweringError::Other(
+ "Stringifier must return string".into(),
+ ));
+ }
+ }
+ SpecialMethod::Comparison => {
+ if method.params.len() != 1 {
+ errors.push(LoweringError::Other(
+ "Comparator must have single parameter".into(),
+ ));
+ }
+ if special_method_presence.comparator {
+ errors.push(LoweringError::Other(
+ "Cannot define two comparators on the same type".into(),
+ ));
+ }
+ special_method_presence.comparator = true;
+ // In the long run we can actually support heterogeneous comparators. Not a priority right now.
+ const COMPARATOR_ERROR: &str =
+ "Comparator's parameter must be identical to self";
+ if let Some(ref selfty) = method.param_self {
+ if let Some(param) = method.params.first() {
+ match (&selfty.ty, ¶m.ty) {
+ (SelfType::Opaque(p), Type::Opaque(p2)) => {
+ if p.tcx_id != p2.tcx_id {
+ errors.push(LoweringError::Other(
+ COMPARATOR_ERROR.into(),
+ ));
+ }
+
+ if p.owner.mutability != Mutability::Immutable
+ || p2.owner.mutability != Mutability::Immutable
+ {
+ errors.push(LoweringError::Other(
+ "comparators must accept immutable parameters"
+ .into(),
+ ));
+ }
+
+ if p2.optional.0 {
+ errors.push(LoweringError::Other(
+ "comparators must accept non-optional parameters"
+ .into(),
+ ));
+ }
+ }
+ (SelfType::Struct(p), Type::Struct(p2)) => {
+ if p.tcx_id != p2.tcx_id {
+ errors.push(LoweringError::Other(
+ COMPARATOR_ERROR.into(),
+ ));
+ }
+ }
+ (SelfType::Enum(p), Type::Enum(p2)) => {
+ if p.tcx_id != p2.tcx_id {
+ errors.push(LoweringError::Other(
+ COMPARATOR_ERROR.into(),
+ ));
+ }
+ }
+ _ => {
+ errors.push(LoweringError::Other(COMPARATOR_ERROR.into()));
+ }
+ }
+ }
+ } else {
+ errors
+ .push(LoweringError::Other("Comparator must be non-static".into()));
+ }
+ }
+ SpecialMethod::Iterator => {
+ if special_method_presence.iterator.is_some() {
+ errors.push(LoweringError::Other(
+ "Cannot mark type as iterator twice".into(),
+ ));
+ }
+ if !method.params.is_empty() {
+ errors.push(LoweringError::Other(
+ "Iterators cannot take parameters".into(),
+ ))
+ }
+ // In theory we could support struct and enum iterators. The benefit is slight:
+ // it generates probably inefficient code whilst being rather weird when it comes to the
+ // "structs and enums convert across the boundary" norm for backends.
+ //
+ // Essentially, the `&mut self` behavior won't work right.
+ //
+ // Furthermore, in some backends (like Dart) defining an iterator may requiring adding fields,
+ // which may not be possible for enums, and would still be an odd-one-out field for structs.g s
+ if let Some(this) = &method.param_self {
+ if !matches!(this.ty, SelfType::Opaque(..)) {
+ errors.push(LoweringError::Other(
+ "Iterators only allowed on opaques".into(),
+ ))
+ }
+ } else {
+ errors.push(LoweringError::Other("Iterators must take self".into()))
+ }
+
+ if let ReturnType::Nullable(ref o) = method.output {
+ if let SuccessType::Unit = o {
+ errors.push(LoweringError::Other(
+ "Iterator method must return something".into(),
+ ));
+ }
+ special_method_presence.iterator = Some(o.clone());
+ } else if let ReturnType::Infallible(SuccessType::OutType(
+ crate::hir::OutType::Opaque(
+ ref o @ crate::hir::OpaquePath {
+ optional: crate::hir::Optional(true),
+ ..
+ },
+ ),
+ )) = method.output
+ {
+ let mut o = o.clone();
+ o.optional = crate::hir::Optional(false);
+
+ special_method_presence.iterator =
+ Some(SuccessType::OutType(crate::hir::OutType::Opaque(o)));
+ } else {
+ errors.push(LoweringError::Other(
+ "Iterator method must return nullable value".into(),
+ ));
+ }
+ }
+ SpecialMethod::Iterable => {
+ if special_method_presence.iterable.is_some() {
+ errors.push(LoweringError::Other(
+ "Cannot mark type as iterable twice".into(),
+ ));
+ }
+ if !method.params.is_empty() {
+ errors.push(LoweringError::Other(
+ "Iterables cannot take parameters".into(),
+ ))
+ }
+ if method.param_self.is_none() {
+ errors.push(LoweringError::Other("Iterables must take self".into()))
+ }
+
+ match method.output.success_type() {
+ SuccessType::OutType(ty) => {
+ if let Some(TypeId::Opaque(id)) = ty.id() {
+ special_method_presence.iterable = Some(id);
+ } else {
+ errors.push(LoweringError::Other(
+ "Iterables must return a custom opaque type".into(),
+ ))
+ }
+ }
+ _ => errors.push(LoweringError::Other(
+ "Iterables must return a custom type".into(),
+ )),
+ }
+ }
+ SpecialMethod::Indexer => {
+ if method.params.len() != 1 {
+ errors.push(LoweringError::Other(
+ "Indexer must have exactly one parameter".into(),
+ ));
+ }
+
+ if method.output.success_type().is_unit() {
+ errors.push(LoweringError::Other("Indexer must return a value".into()));
+ }
+ }
+ }
+ } else {
+ errors.push(LoweringError::Other(format!("Special method (type {special:?}) not allowed on non-method context {context:?}")))
+ }
+ }
+
+ if namespace.is_some()
+ && matches!(
+ context,
+ AttributeContext::Method(..) | AttributeContext::EnumVariant(..)
+ )
+ {
+ errors.push(LoweringError::Other(
+ "`namespace` can only be used on types".to_string(),
+ ));
+ }
+
+ if matches!(
+ context,
+ AttributeContext::Param | AttributeContext::SelfParam | AttributeContext::Field
+ ) {
+ if *disable {
+ errors.push(LoweringError::Other(format!(
+ "`disable`s cannot be used on an {context:?}."
+ )));
+ }
+
+ if namespace.is_some() {
+ errors.push(LoweringError::Other(format!(
+ "`namespace` cannot be used on an {context:?}."
+ )));
+ }
+
+ if !rename.is_empty() || !abi_rename.is_empty() {
+ errors.push(LoweringError::Other(format!(
+ "`rename`s cannot be used on an {context:?}."
+ )));
+ }
+
+ if special_method.is_some() {
+ errors.push(LoweringError::Other(format!(
+ "{context:?} cannot be special methods."
+ )));
+ }
+ }
+ }
+
+ pub(crate) fn for_inheritance(&self, context: AttrInheritContext) -> Attrs {
+ let rename = self.rename.attrs_for_inheritance(context, false);
+
+ // Disabling shouldn't inherit to variants
+ let disable = if context == AttrInheritContext::Variant {
+ false
+ } else {
+ self.disable
+ };
+ let namespace = if matches!(
+ context,
+ AttrInheritContext::Module | AttrInheritContext::Type
+ ) {
+ self.namespace.clone()
+ } else {
+ None
+ };
+
+ Attrs {
+ disable,
+ rename,
+ namespace,
+ // Was already inherited on the AST side
+ abi_rename: Default::default(),
+ // Never inherited
+ special_method: None,
+ demo_attrs: Default::default(),
+ }
+ }
+}
+
+/// Non-exhaustive list of what attributes and other features your backend is able to handle, based on #[diplomat::attr(...)] contents.
+/// Set this through an [`AttributeValidator`].
+///
+/// See [`SpecialMethod`] and [`Attrs`] for your specific implementation needs.
+///
+/// For example, the current dart backend supports [`BackendAttrSupport::constructors`]. So when it encounters:
+/// ```ignore
+/// struct Sample {}
+/// impl Sample {
+/// #[diplomat::attr(constructor)]
+/// pub fn new() -> Box<Self> {
+/// Box::new(Sample{})
+/// }
+/// }
+///
+/// ```
+///
+/// It generates
+/// ```dart
+/// factory Sample()
+/// ```
+///
+/// If a backend does not support a specific `#[diplomat::attr(...)]`, it may error.
+#[non_exhaustive]
+#[derive(Copy, Clone, Debug, Default)]
+pub struct BackendAttrSupport {
+ /// Namespacing types, e.g. C++ `namespace`.
+ pub namespacing: bool,
+ /// Rust can directly acccess the memory of this language, like C and C++.
+ /// This is not supported in any garbage-collected language.
+ pub memory_sharing: bool,
+ /// This language's structs are non-exhaustive by default, i.e. adding
+ /// fields is not a breaking change.
+ pub non_exhaustive_structs: bool,
+ /// Whether the language supports method overloading
+ pub method_overloading: bool,
+ /// Whether the language uses UTF-8 strings
+ pub utf8_strings: bool,
+ /// Whether the language uses UTF-16 strings
+ pub utf16_strings: bool,
+ /// Whether the language supports using slices with 'static lifetimes.
+ pub static_slices: bool,
+
+ // Special methods
+ /// Marking a method as a constructor to generate special constructor methods.
+ pub constructors: bool,
+ /// Marking a method as a named constructor to generate special named constructor methods.
+ pub named_constructors: bool,
+ /// Marking constructors as being able to return errors. This is possible in languages where
+ /// errors are thrown as exceptions (Dart), but not for example in C++, where errors are
+ /// returned as values (constructors usually have to return the type itself).
+ pub fallible_constructors: bool,
+ /// Marking methods as field getters and setters, see [`SpecialMethod::Getter`] and [`SpecialMethod::Setter`]
+ pub accessors: bool,
+ /// Marking a method as the `to_string` method, which is special in this language.
+ pub stringifiers: bool,
+ /// Marking a method as the `compare_to` method, which is special in this language.
+ pub comparators: bool,
+ /// Marking a method as the `next` method, which is special in this language.
+ pub iterators: bool,
+ /// Marking a method as the `iterator` method, which is special in this language.
+ pub iterables: bool,
+ /// Marking a method as the `[]` operator, which is special in this language.
+ pub indexing: bool,
+
+ /// Support for Option<Struct> and Option<Primitive>
+ pub option: bool,
+ /// Allowing callback arguments
+ pub callbacks: bool,
+ /// Allowing traits
+ pub traits: bool,
+}
+
+impl BackendAttrSupport {
+ #[cfg(test)]
+ fn all_true() -> Self {
+ Self {
+ namespacing: true,
+ memory_sharing: true,
+ non_exhaustive_structs: true,
+ method_overloading: true,
+ utf8_strings: true,
+ utf16_strings: true,
+ static_slices: true,
+
+ constructors: true,
+ named_constructors: true,
+ fallible_constructors: true,
+ accessors: true,
+ stringifiers: true,
+ comparators: true,
+ iterators: true,
+ iterables: true,
+ indexing: true,
+ option: true,
+ callbacks: true,
+ traits: true,
+ }
+ }
+}
+
+/// Defined by backends when validating attributes
+pub trait AttributeValidator {
+ /// The primary name of the backend, for use in diagnostics
+ fn primary_name(&self) -> &str;
+ /// Does this backend satisfy `cfg(backend_name)`?
+ /// (Backends are allowed to satisfy multiple backend names, useful when there
+ /// are multiple backends for a language)
+ fn is_backend(&self, backend_name: &str) -> bool;
+ /// does this backend satisfy cfg(name = value)?
+ fn is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError>;
+ /// What backedn attrs does this support?
+ fn attrs_supported(&self) -> BackendAttrSupport;
+
+ /// Provided, checks if type satisfies a `DiplomatBackendAttrCfg`
+ ///
+ /// auto_found helps check for `auto`, which is only allowed within `any` and at the top level. When `None`,
+ /// `auto` is not allowed.
+ fn satisfies_cfg(
+ &self,
+ cfg: &DiplomatBackendAttrCfg,
+ mut auto_found: Option<&mut bool>,
+ ) -> Result<bool, LoweringError> {
+ Ok(match *cfg {
+ DiplomatBackendAttrCfg::Not(ref c) => !self.satisfies_cfg(c, None)?,
+ DiplomatBackendAttrCfg::Any(ref cs) => {
+ #[allow(clippy::needless_option_as_deref)]
+ // False positive: we need this for reborrowing
+ for c in cs {
+ if self.satisfies_cfg(c, auto_found.as_deref_mut())? {
+ return Ok(true);
+ }
+ }
+ false
+ }
+ DiplomatBackendAttrCfg::All(ref cs) => {
+ for c in cs {
+ if !self.satisfies_cfg(c, None)? {
+ return Ok(false);
+ }
+ }
+ true
+ }
+ DiplomatBackendAttrCfg::Auto => {
+ if let Some(found) = auto_found {
+ *found = true;
+ return Ok(true);
+ } else {
+ return Err(LoweringError::Other("auto in diplomat::attr() is only allowed at the top level and within `any`".into()));
+ }
+ }
+ DiplomatBackendAttrCfg::Star => true,
+ DiplomatBackendAttrCfg::BackendName(ref n) => self.is_backend(n),
+ DiplomatBackendAttrCfg::NameValue(ref n, ref v) => self.is_name_value(n, v)?,
+ })
+ }
+
+ // Provided, constructs an attribute
+ fn attr_from_ast(
+ &self,
+ ast: &ast::Attrs,
+ parent_attrs: &Attrs,
+ errors: &mut ErrorStore,
+ ) -> Attrs {
+ Attrs::from_ast(ast, self, parent_attrs, errors)
+ }
+
+ // Provided: validates an attribute in the context in which it was constructed
+ fn validate(&self, attrs: &Attrs, context: AttributeContext, errors: &mut ErrorStore) {
+ attrs.validate(self, context, errors)
+ }
+}
+
+/// A basic attribute validator
+#[non_exhaustive]
+#[derive(Default)]
+pub struct BasicAttributeValidator {
+ /// The primary name of this backend (should be unique, ideally)
+ pub backend_name: String,
+ /// The attributes supported
+ pub support: BackendAttrSupport,
+ /// Additional names for this backend
+ pub other_backend_names: Vec<String>,
+ /// override is_name_value()
+ #[allow(clippy::type_complexity)] // dyn fn is not that complex
+ pub is_name_value: Option<Box<dyn Fn(&str, &str) -> bool>>,
+}
+
+impl BasicAttributeValidator {
+ pub fn new(backend_name: &str) -> Self {
+ BasicAttributeValidator {
+ backend_name: backend_name.into(),
+ ..Self::default()
+ }
+ }
+}
+
+impl AttributeValidator for BasicAttributeValidator {
+ fn primary_name(&self) -> &str {
+ &self.backend_name
+ }
+ fn is_backend(&self, backend_name: &str) -> bool {
+ self.backend_name == backend_name
+ || self.other_backend_names.iter().any(|n| n == backend_name)
+ }
+ fn is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError> {
+ Ok(if name == "supports" {
+ // destructure so new fields are forced to be added
+ let BackendAttrSupport {
+ namespacing,
+ memory_sharing,
+ non_exhaustive_structs,
+ method_overloading,
+ utf8_strings,
+ utf16_strings,
+ static_slices,
+
+ constructors,
+ named_constructors,
+ fallible_constructors,
+ accessors,
+ stringifiers,
+ comparators,
+ iterators,
+ iterables,
+ indexing,
+ option,
+ callbacks,
+ traits,
+ } = self.support;
+ match value {
+ "namespacing" => namespacing,
+ "memory_sharing" => memory_sharing,
+ "non_exhaustive_structs" => non_exhaustive_structs,
+ "method_overloading" => method_overloading,
+ "utf8_strings" => utf8_strings,
+ "utf16_strings" => utf16_strings,
+ "static_slices" => static_slices,
+
+ "constructors" => constructors,
+ "named_constructors" => named_constructors,
+ "fallible_constructors" => fallible_constructors,
+ "accessors" => accessors,
+ "stringifiers" => stringifiers,
+ "comparators" => comparators,
+ "iterators" => iterators,
+ "iterables" => iterables,
+ "indexing" => indexing,
+ "option" => option,
+ "callbacks" => callbacks,
+ "traits" => traits,
+ _ => {
+ return Err(LoweringError::Other(format!(
+ "Unknown supports = value found: {value}"
+ )))
+ }
+ }
+ } else if let Some(ref nv) = self.is_name_value {
+ nv(name, value)
+ } else {
+ false
+ })
+ }
+ fn attrs_supported(&self) -> BackendAttrSupport {
+ self.support
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::hir;
+ use std::fmt::Write;
+
+ macro_rules! uitest_lowering_attr {
+ ($attrs:expr, $($file:tt)*) => {
+ let parsed: syn::File = syn::parse_quote! { $($file)* };
+
+ let mut output = String::new();
+
+ let mut attr_validator = hir::BasicAttributeValidator::new("tests");
+ attr_validator.support = $attrs;
+ match hir::TypeContext::from_syn(&parsed, attr_validator) {
+ Ok(_context) => (),
+ Err(e) => {
+ for (ctx, err) in e {
+ writeln!(&mut output, "Lowering error in {ctx}: {err}").unwrap();
+ }
+ }
+ };
+ insta::with_settings!({}, {
+ insta::assert_snapshot!(output)
+ });
+ }
+ }
+
+ #[test]
+ fn test_auto() {
+ uitest_lowering_attr! { hir::BackendAttrSupport { comparators: true, ..Default::default()},
+ #[diplomat::bridge]
+ mod ffi {
+ use std::cmp;
+
+ #[diplomat::opaque]
+ #[diplomat::attr(auto, namespace = "should_not_show_up")]
+ struct Opaque;
+
+
+ impl Opaque {
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparator_static(&self, other: &Opaque) -> cmp::Ordering {
+ todo!()
+ }
+ #[diplomat::attr(*, iterator)]
+ pub fn next(&mut self) -> Option<u8> {
+ self.0.next()
+ }
+ #[diplomat::attr(auto, rename = "bar")]
+ pub fn auto_doesnt_work_on_renames(&self) {
+ }
+ #[diplomat::attr(auto, disable)]
+ pub fn auto_doesnt_work_on_disables(&self) {
+ }
+ }
+
+ }
+ }
+ }
+
+ #[test]
+ fn test_comparator() {
+ uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
+ #[diplomat::bridge]
+ mod ffi {
+ use std::cmp;
+
+ #[diplomat::opaque]
+ struct Opaque;
+
+ struct Struct {
+ field: u8
+ }
+
+
+ impl Opaque {
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparator_static(other: &Opaque) -> cmp::Ordering {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparator_none(&self) -> cmp::Ordering {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparator_othertype(other: Struct) -> cmp::Ordering {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparator_badreturn(&self, other: &Opaque) -> u8 {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparison_correct(&self, other: &Opaque) -> cmp::Ordering {
+ todo!()
+ }
+ pub fn comparison_unmarked(&self, other: &Opaque) -> cmp::Ordering {
+ todo!()
+ }
+ pub fn ordering_wrong(&self, other: cmp::Ordering) {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparison_mut(&self, other: &mut Opaque) -> cmp::Ordering {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparison_opt(&self, other: Option<&Opaque>) -> cmp::Ordering {
+ todo!()
+ }
+ }
+
+ impl Struct {
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparison_other(self, other: &Opaque) -> cmp::Ordering {
+ todo!()
+ }
+ #[diplomat::attr(auto, comparison)]
+ pub fn comparison_correct(self, other: Self) -> cmp::Ordering {
+ todo!()
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn test_iterator() {
+ uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
+ #[diplomat::bridge]
+ mod ffi {
+
+ #[diplomat::opaque]
+ struct Opaque(Vec<u8>);
+ #[diplomat::opaque]
+ struct OpaqueIterator<'a>(std::slice::Iter<'a>);
+
+
+ impl Opaque {
+ #[diplomat::attr(auto, iterable)]
+ pub fn iterable<'a>(&'a self) -> Box<OpaqueIterator<'a>> {
+ Box::new(OpaqueIterator(self.0.iter()))
+ }
+ }
+
+ impl OpaqueIterator {
+ #[diplomat::attr(auto, iterator)]
+ pub fn next(&mut self) -> Option<u8> {
+ self.0.next()
+ }
+ }
+
+ #[diplomat::opaque]
+ struct Broken;
+
+ impl Broken {
+ #[diplomat::attr(auto, iterable)]
+ pub fn iterable_no_return(&self) {}
+ #[diplomat::attr(auto, iterable)]
+ pub fn iterable_no_self() -> Box<BrokenIterator> { todo!() }
+
+ #[diplomat::attr(auto, iterable)]
+ pub fn iterable_non_custom(&self) -> u8 { todo!() }
+ }
+
+ #[diplomat::opaque]
+ struct BrokenIterator;
+
+ impl BrokenIterator {
+ #[diplomat::attr(auto, iterator)]
+ pub fn iterator_no_return(&self) {}
+ #[diplomat::attr(auto, iterator)]
+ pub fn iterator_no_self() -> Option<u8> { todo!() }
+
+ #[diplomat::attr(auto, iterator)]
+ pub fn iterator_no_option(&self) -> u8 { todo!() }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn test_unsupported_features() {
+ uitest_lowering_attr! { hir::BackendAttrSupport::default(),
+ #[diplomat::bridge]
+ mod ffi {
+ use std::cmp;
+ use diplomat_runtime::DiplomatOption;
+
+ #[diplomat::opaque]
+ struct Opaque;
+
+ struct Struct {
+ pub a: u8,
+ pub b: u8,
+ pub c: DiplomatOption<u8>,
+ }
+
+ struct Struct2 {
+ pub a: DiplomatOption<Struct>,
+ }
+
+ #[diplomat::out]
+ struct OutStruct {
+ pub option: DiplomatOption<u8>
+ }
+
+
+ impl Opaque {
+ pub fn take_option(&self, option: DiplomatOption<u8>) {
+ todo!()
+ }
+ // Always ok since this translates to a Resulty return
+ pub fn returning_option_is_ok(&self) -> Option<u8> {
+ todo!()
+ }
+ }
+
+ }
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/hir/defs.rs b/crates/diplomat_core/src/hir/defs.rs
new file mode 100644
index 0000000..90f1960
--- /dev/null
+++ b/crates/diplomat_core/src/hir/defs.rs
@@ -0,0 +1,253 @@
+//! Type definitions for structs, output structs, opaque structs, and enums.
+
+use super::lifetimes::LifetimeEnv;
+use super::{
+ Attrs, Callback, Everywhere, IdentBuf, Method, OutputOnly, SpecialMethodPresence, TyPosition,
+ Type,
+};
+use crate::ast::Docs;
+
+#[non_exhaustive]
+pub enum ReturnableStructDef<'tcx> {
+ Struct(&'tcx StructDef),
+ OutStruct(&'tcx OutStructDef),
+}
+
+#[derive(Copy, Clone, Debug)]
+#[non_exhaustive]
+pub enum TypeDef<'tcx> {
+ Struct(&'tcx StructDef),
+ OutStruct(&'tcx OutStructDef),
+ Opaque(&'tcx OpaqueDef),
+ Enum(&'tcx EnumDef),
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct TraitDef {
+ // TyPosition: InputOnly
+ pub docs: Docs,
+ pub name: IdentBuf,
+ pub methods: Vec<Callback>,
+ pub attrs: Attrs,
+ pub lifetimes: LifetimeEnv,
+}
+
+/// Structs that can only be returned from methods.
+pub type OutStructDef = StructDef<OutputOnly>;
+
+/// Structs that can be either inputs or outputs in methods.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct StructDef<P: TyPosition = Everywhere> {
+ pub docs: Docs,
+ pub name: IdentBuf,
+ pub fields: Vec<StructField<P>>,
+ pub methods: Vec<Method>,
+ pub attrs: Attrs,
+ pub lifetimes: LifetimeEnv,
+ pub special_method_presence: SpecialMethodPresence,
+}
+
+/// A struct whose contents are opaque across the FFI boundary, and can only
+/// cross when behind a pointer.
+///
+/// All opaques can be inputs or outputs when behind a reference, but owned
+/// opaques can only be returned since there isn't a general way for most languages
+/// to give up ownership.
+///
+/// A struct marked with `#[diplomat::opaque]`.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct OpaqueDef {
+ pub docs: Docs,
+ pub name: IdentBuf,
+ pub methods: Vec<Method>,
+ pub attrs: Attrs,
+ pub lifetimes: LifetimeEnv,
+ pub special_method_presence: SpecialMethodPresence,
+
+ /// The ABI name of the generated destructor
+ pub dtor_abi_name: IdentBuf,
+}
+
+/// The enum type.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct EnumDef {
+ pub docs: Docs,
+ pub name: IdentBuf,
+ pub variants: Vec<EnumVariant>,
+ pub methods: Vec<Method>,
+ pub attrs: Attrs,
+ pub special_method_presence: SpecialMethodPresence,
+}
+
+/// A field on a [`OutStruct`]s.
+pub type OutStructField = StructField<OutputOnly>;
+
+/// A field on a [`Struct`]s.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct StructField<P: TyPosition = Everywhere> {
+ pub docs: Docs,
+ pub name: IdentBuf,
+ pub ty: Type<P>,
+ pub attrs: Attrs,
+}
+
+/// A variant of an [`Enum`].
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct EnumVariant {
+ pub docs: Docs,
+ pub name: IdentBuf,
+ pub discriminant: isize,
+ pub attrs: Attrs,
+}
+
+impl TraitDef {
+ pub(super) fn new(
+ docs: Docs,
+ name: IdentBuf,
+ methods: Vec<Callback>,
+ attrs: Attrs,
+ lifetimes: LifetimeEnv,
+ ) -> Self {
+ Self {
+ docs,
+ name,
+ methods,
+ attrs,
+ lifetimes,
+ }
+ }
+}
+
+impl<P: TyPosition> StructDef<P> {
+ pub(super) fn new(
+ docs: Docs,
+ name: IdentBuf,
+ fields: Vec<StructField<P>>,
+ methods: Vec<Method>,
+ attrs: Attrs,
+ lifetimes: LifetimeEnv,
+ special_method_presence: SpecialMethodPresence,
+ ) -> Self {
+ Self {
+ docs,
+ name,
+ fields,
+ methods,
+ attrs,
+ lifetimes,
+ special_method_presence,
+ }
+ }
+}
+
+impl OpaqueDef {
+ pub(super) fn new(
+ docs: Docs,
+ name: IdentBuf,
+ methods: Vec<Method>,
+ attrs: Attrs,
+ lifetimes: LifetimeEnv,
+ special_method_presence: SpecialMethodPresence,
+ dtor_abi_name: IdentBuf,
+ ) -> Self {
+ Self {
+ docs,
+ name,
+ methods,
+ attrs,
+ lifetimes,
+ special_method_presence,
+ dtor_abi_name,
+ }
+ }
+}
+
+impl EnumDef {
+ pub(super) fn new(
+ docs: Docs,
+ name: IdentBuf,
+ variants: Vec<EnumVariant>,
+ methods: Vec<Method>,
+ attrs: Attrs,
+ special_method_presence: SpecialMethodPresence,
+ ) -> Self {
+ Self {
+ docs,
+ name,
+ variants,
+ methods,
+ attrs,
+ special_method_presence,
+ }
+ }
+}
+
+impl<'a, P: TyPosition> From<&'a StructDef<P>> for TypeDef<'a> {
+ fn from(x: &'a StructDef<P>) -> Self {
+ P::wrap_struct_def(x)
+ }
+}
+
+impl<'a> From<&'a OpaqueDef> for TypeDef<'a> {
+ fn from(x: &'a OpaqueDef) -> Self {
+ TypeDef::Opaque(x)
+ }
+}
+
+impl<'a> From<&'a EnumDef> for TypeDef<'a> {
+ fn from(x: &'a EnumDef) -> Self {
+ TypeDef::Enum(x)
+ }
+}
+
+impl<'tcx> TypeDef<'tcx> {
+ pub fn name(&self) -> &'tcx IdentBuf {
+ match *self {
+ Self::Struct(ty) => &ty.name,
+ Self::OutStruct(ty) => &ty.name,
+ Self::Opaque(ty) => &ty.name,
+ Self::Enum(ty) => &ty.name,
+ }
+ }
+
+ pub fn docs(&self) -> &'tcx Docs {
+ match *self {
+ Self::Struct(ty) => &ty.docs,
+ Self::OutStruct(ty) => &ty.docs,
+ Self::Opaque(ty) => &ty.docs,
+ Self::Enum(ty) => &ty.docs,
+ }
+ }
+ pub fn methods(&self) -> &'tcx [Method] {
+ match *self {
+ Self::Struct(ty) => &ty.methods,
+ Self::OutStruct(ty) => &ty.methods,
+ Self::Opaque(ty) => &ty.methods,
+ Self::Enum(ty) => &ty.methods,
+ }
+ }
+
+ pub fn attrs(&self) -> &'tcx Attrs {
+ match *self {
+ Self::Struct(ty) => &ty.attrs,
+ Self::OutStruct(ty) => &ty.attrs,
+ Self::Opaque(ty) => &ty.attrs,
+ Self::Enum(ty) => &ty.attrs,
+ }
+ }
+
+ pub fn special_method_presence(&self) -> &'tcx SpecialMethodPresence {
+ match *self {
+ Self::Struct(ty) => &ty.special_method_presence,
+ Self::OutStruct(ty) => &ty.special_method_presence,
+ Self::Opaque(ty) => &ty.special_method_presence,
+ Self::Enum(ty) => &ty.special_method_presence,
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/hir/elision.rs b/crates/diplomat_core/src/hir/elision.rs
new file mode 100644
index 0000000..90387fb
--- /dev/null
+++ b/crates/diplomat_core/src/hir/elision.rs
@@ -0,0 +1,614 @@
+//! This module provides the functionality for lowering lifetimes from the AST
+//! to the HIR, while simultaneously inferencing elided lifetimes.
+//!
+//! Full elision rules can be found in the [Nomicon].
+//!
+//! The key factor about lifetime elision is that all elision in the output of
+//! the method (if there is any) corresponds to exactly one lifetime in the method
+//! arguments, which may or may not be elided. Therefore, our task is to find this
+//! potential lifetime first, so that if we encounter an elided lifetime while
+//! lowering the output, we know which lifetime it corresponds to.
+//!
+//! # Unspoken Rules of Elision.
+//!
+//! Broadly speaking, the Nomicon defines the elision rules are such:
+//! 1. If there's a `&self` or `&mut self`, the lifetime of that borrow
+//! corresponds to elision in the output.
+//! 2. Otherwise, if there's exactly one lifetime in the input, then that lifetime
+//! corresponds to elision in the output.
+//! 3. If neither of these cases hold, then the output cannot contain elision.
+//!
+//! What the Nomicon doesn't tell you is that there are weird corner cases around
+//! using the `Self` type. Specifically, lifetimes in the `Self` type and in the
+//! type of the `self` argument (optional) aren't considered when figuring out
+//! which lifetime should correspond to elision in the output.
+//!
+//! Check out the following code:
+//! ```compile_fail
+//! struct Foo<'a>(&'a str);
+//!
+//! impl<'a> Foo<'a> {
+//! fn get(self) -> &str { self.0 }
+//! }
+//! ```
+//! This code will fail to compile because it doesn't look at the `'a` in the
+//! `Foo<'a>`, which is what the type of `self` expands to. Therefore, it will
+//! conclude that there's nothing for the output to borrow from.
+//! This can be fixed by returning `&'a str` though. Many of the design
+//! decisions in this module were made to be able to replicate this behavior.
+//!
+//! You may be asking "why would we care about rejecting code that rustc rejects
+//! before it reaches us?" And the answer is this:
+//! ```rust
+//! # struct Foo<'a>(&'a str);
+//! impl<'a> Foo<'a> {
+//! fn get(self, s: &str) -> &str { s }
+//! }
+//! ```
+//! This code is accepted by rustc, since it only considers the lifetime of `s`
+//! when searching for a lifetime that corresponds to output elision. If we were
+//! to naively look at all the lifetimes, we would see the lifetime in the `self`
+//! argument and the lifetime of `s`, making us reject this method. Therefore, we
+//! have to be extremely careful when traversing lifetimes, and make sure that
+//! lifetimes of `Self` are lowered but _not_ considered for elision, while other
+//! lifetimes are lowered while also being considered for elision.
+//!
+//! # Lowering and Inference
+//!
+//! Lowering and elision inference is broken into three distinct stages:
+//! 1. Lowering the borrow in `&self` or `&mut self`, if there is one.
+//! 2. Lowering lifetimes of other params.
+//! 3. Lowering lifetimes of the output.
+//!
+//! Although each stage fundementally lowers lifetimes, they behave differently
+//! when lowering elided lifetimes. Naturally, this module represents each stage
+//! as a state in a state machine.
+//!
+//! The first state is represented by the [`SelfParamLifetimeLowerer`] type.
+//! Since there is either zero or one occurrences of `&self` or `&mut self`, it
+//! exposes the `.no_self_ref()` and `.lower_self_ref(lt)` methods respectively,
+//! which consume the `SelfParamLifetimeLowerer` and return the next state,
+//! [`ParamLifetimeLowerer`], as well as the lowered lifetime. The reason these
+//! are two distinct types is that the lifetime in `&self` and `&mut self` takes
+//! precedence over any other lifetimes in the input, so `.lower_self_ref(lt)`
+//! tells the next state that the candidate lifetime is already found, and to
+//! generate fresh anonymous lifetimes for any elided lifetimes.
+//!
+//! The second state is represented by the [`ParamLifetimeLowerer`] type.
+//! It implements a helper trait, [`LifetimeLowerer`], which abstracts the lowering
+//! of references and generic lifetimes. Internally, it wraps an [`ElisionSource`],
+//! which acts as a state machine for tracking candidate lifetimes to correspond
+//! to elision in the output. When a lifetime that's not in the type of the `self`
+//! argument or in the expanded generics of the `Self` type is visited, this
+//! state machine is potentially updated to another state. If the lifetime is
+//! anonymous, it's added to the internal list of nodes that go into the final
+//! [`LifetimeEnv`] after lowering. Once all the lifetimes in the input are
+//! lowered, the `into_return_ltl()` method is called to transition into the
+//! final state.
+//!
+//! The third and final state is represented by the [`ReturnLifetimeLowerer`] type.
+//! Similar to `ParamLifetimeLowerer`, it also implements the [`LifetimeLowerer`]
+//! helper trait. However, it differs from `ParamLifetimeLowerer` since instead
+//! of potentially updating the internal `ElisionSource` when visiting a lifetime,
+//! it instead reads from it when an elided lifetime occurs. Once all the output
+//! lifetimes are lowered, `.finish()` is called to return the finalized
+//! [`LifetimeEnv`].
+//!
+//! [Nomicon]: https://doc.rust-lang.org/nomicon/lifetime-elision.html
+
+use super::lifetimes::{BoundedLifetime, Lifetime, LifetimeEnv, Lifetimes, MaybeStatic};
+use super::LoweringContext;
+use crate::ast;
+use smallvec::SmallVec;
+
+/// Lower [`ast::Lifetime`]s to [`Lifetime`]s.
+///
+/// This helper traits allows the [`lower_type`] and [`lower_out_type`] methods
+/// to abstractly lower lifetimes without concern for what sort of tracking
+/// goes on. In particular, elision inference requires updating internal state
+/// when visiting lifetimes in the input.
+pub trait LifetimeLowerer {
+ /// Lowers an [`ast::Lifetime`].
+ fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime>;
+
+ /// Lowers a slice of [`ast::Lifetime`]s by calling
+ /// [`LifetimeLowerer::lower_lifetime`] repeatedly.
+ ///
+
+ /// `type_generics` is the full list of generics on the type definition of the type
+ /// this lifetimes list is found on (needed for generating anon lifetimes)
+ fn lower_lifetimes(
+ &mut self,
+ lifetimes: &[ast::Lifetime],
+ type_generics: &ast::LifetimeEnv,
+ ) -> Lifetimes {
+ let mut lifetimes = Lifetimes::from_fn(lifetimes, |lifetime| self.lower_lifetime(lifetime));
+
+ for _ in lifetimes.as_slice().len()..type_generics.nodes.len() {
+ lifetimes.append_lifetime(self.lower_lifetime(&ast::Lifetime::Anonymous))
+ }
+ lifetimes
+ }
+
+ /// Lowers a slice of [`ast::Lifetime`], where the strategy may vary depending
+ /// on whether or not the lifetimes are expanded from the `Self` type.
+ ///
+ /// The distinction between this and [`LifetimeLowerer::lower_lifetimes`] is
+ /// that if `Self` expands to a type with anonymous lifetimes like `Foo<'_>`,
+ /// then multiple instances of `Self` should expand to have the same anonymous
+ /// lifetime, and this lifetime can be cached inside of the `self` argument.
+ /// Additionally, elision inferences knows to not search inside the generics
+ /// of `Self` types for candidate lifetimes to correspond to elided lifetimes
+ /// in the output.
+ ///
+ /// `type_generics` is the full list of generics on the type definition of the type
+ /// this generics list is found on (needed for generating anon lifetimes)
+
+ fn lower_generics(
+ &mut self,
+ lifetimes: &[ast::Lifetime],
+ type_generics: &ast::LifetimeEnv,
+ is_self: bool,
+ ) -> Lifetimes;
+}
+
+/// A state machine for tracking which lifetime in a function's parameters
+/// may correspond to elided lifetimes in the output.
+#[derive(Copy, Clone)]
+enum ElisionSource {
+ /// No borrows in the input, no elision.
+ NoBorrows,
+ /// `&self` or `&mut self`, elision allowed.
+ SelfParam(MaybeStatic<Lifetime>),
+ /// One param contains a borrow, elision allowed.
+ OneParam(MaybeStatic<Lifetime>),
+ /// Multiple borrows and no self borrow, no elision.
+ MultipleBorrows,
+}
+
+impl ElisionSource {
+ /// Potentially transition to a new state.
+ fn visit_lifetime(&mut self, lifetime: MaybeStatic<Lifetime>) {
+ match self {
+ ElisionSource::NoBorrows => *self = ElisionSource::OneParam(lifetime),
+ ElisionSource::SelfParam(_) => {
+ // References to self have the highest precedence, do nothing.
+ }
+ ElisionSource::OneParam(_) => *self = ElisionSource::MultipleBorrows,
+ ElisionSource::MultipleBorrows => {
+ // There's ambiguity. This is valid when there's no elision in
+ // the output.
+ }
+ };
+ }
+}
+
+/// A type for storing shared information between the different states of elision
+/// inference.
+///
+/// This contains data for generating fresh elided lifetimes, looking up named
+/// lifetimes, and caching lifetimes of `Self`.
+pub(super) struct BaseLifetimeLowerer<'ast> {
+ lifetime_env: &'ast ast::LifetimeEnv,
+ self_lifetimes: Option<Lifetimes>,
+ nodes: SmallVec<[BoundedLifetime; super::lifetimes::INLINE_NUM_LIFETIMES]>,
+ num_lifetimes: usize,
+}
+
+/// The first phase of output elision inference.
+///
+/// In the first phase, the type signature of the `&self` or `&mut self` type
+/// is lowered into its HIR representation, if present. According to elision
+/// rules, this reference has the highest precedence as the lifetime that
+/// goes into elision in the output, and so it's checked first.
+pub(super) struct SelfParamLifetimeLowerer<'ast> {
+ base: BaseLifetimeLowerer<'ast>,
+}
+
+/// The second phase of output elision inference.
+///
+/// In the second phase, all lifetimes in the parameter type signatures
+/// (besides the lifetime of self, if present) are lowered. If a self param
+/// didn't claim the potential output elided lifetime, then if there's a
+/// single lifetime (elided or not) in the inputs, it will claim the
+/// potential output elided lifetime.
+pub(super) struct ParamLifetimeLowerer<'ast> {
+ elision_source: ElisionSource,
+ base: BaseLifetimeLowerer<'ast>,
+}
+
+/// The third and final phase of output elision inference.
+///
+/// In the third phase, the type signature of the output type is lowered into
+/// its HIR representation. If one of the input lifetimes were marked as
+/// responsible for any elision in the output, then anonymous lifetimes get
+/// that lifetime. If none did and there is elision in the output, then
+/// rustc should have errored and said the elision was ambiguous, meaning
+/// that state should be impossible so it panics.
+pub(super) struct ReturnLifetimeLowerer<'ast> {
+ elision_source: ElisionSource,
+ base: BaseLifetimeLowerer<'ast>,
+}
+
+impl<'ast> BaseLifetimeLowerer<'ast> {
+ /// Returns a [`Lifetime`] representing a new anonymous lifetime, and
+ /// pushes it to the nodes vector.
+ fn new_elided(&mut self) -> Lifetime {
+ let index = self.num_lifetimes;
+ self.num_lifetimes += 1;
+ Lifetime::new(index)
+ }
+
+ /// Lowers a single [`ast::Lifetime`]. If the lifetime is elided, then a fresh
+ /// [`ImplicitLifetime`] is generated.
+ fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime> {
+ match lifetime {
+ ast::Lifetime::Static => MaybeStatic::Static,
+ ast::Lifetime::Named(named) => {
+ MaybeStatic::NonStatic(Lifetime::from_ast(named, self.lifetime_env))
+ }
+ ast::Lifetime::Anonymous => MaybeStatic::NonStatic(self.new_elided()),
+ }
+ }
+
+ /// Retrieves the cached `Self` lifetimes, or caches newly generated
+ /// lifetimes and returns those.
+ fn self_lifetimes_or_new(&mut self, ast_lifetimes: &[ast::Lifetime]) -> Lifetimes {
+ if let Some(lifetimes) = &self.self_lifetimes {
+ lifetimes.clone()
+ } else {
+ let lifetimes = Lifetimes::from_fn(ast_lifetimes, |lt| self.lower_lifetime(lt));
+ self.self_lifetimes = Some(lifetimes.clone());
+ lifetimes
+ }
+ }
+}
+
+impl<'ast> SelfParamLifetimeLowerer<'ast> {
+ /// Returns a new [`SelfParamLifetimeLowerer`].
+ pub fn new(
+ lifetime_env: &'ast ast::LifetimeEnv,
+ ctx: &mut LoweringContext,
+ ) -> Result<Self, ()> {
+ let mut hir_nodes = Ok(SmallVec::new());
+
+ for ast_node in lifetime_env.nodes.iter() {
+ let lifetime = ctx.lower_ident(ast_node.lifetime.name(), "named lifetime");
+ match (lifetime, &mut hir_nodes) {
+ (Ok(lifetime), Ok(hir_nodes)) => {
+ hir_nodes.push(BoundedLifetime::new(
+ lifetime,
+ ast_node.longer.iter().map(|i| Lifetime::new(*i)).collect(),
+ ast_node.shorter.iter().map(|i| Lifetime::new(*i)).collect(),
+ ));
+ }
+ _ => hir_nodes = Err(()),
+ }
+ }
+
+ hir_nodes.map(|nodes| Self {
+ base: BaseLifetimeLowerer {
+ lifetime_env,
+ self_lifetimes: None,
+ num_lifetimes: nodes.len(),
+ nodes,
+ },
+ })
+ }
+
+ /// Lowers the lifetime of `&self` or `&mut self`.
+ ///
+ /// The lifetimes of `&self` and `&mut self` are special, because they
+ /// automatically take priority over any other lifetime in the input for
+ /// being tied to any elided lifetimes in the output.
+ ///
+ /// Along with returning the lowered lifetime, this method also returns the
+ /// next state in elision inference, the [`ParamLifetimeLowerer`].
+ pub fn lower_self_ref(
+ mut self,
+ lifetime: &ast::Lifetime,
+ ) -> (MaybeStatic<Lifetime>, ParamLifetimeLowerer<'ast>) {
+ let self_lifetime = self.base.lower_lifetime(lifetime);
+
+ (
+ self_lifetime,
+ self.into_param_ltl(ElisionSource::SelfParam(self_lifetime)),
+ )
+ }
+
+ /// Acknowledges that there's no `&self` or `&mut self`, and transitions
+ /// to the next state, [`ParamLifetimeLowerer`].
+ pub fn no_self_ref(self) -> ParamLifetimeLowerer<'ast> {
+ self.into_param_ltl(ElisionSource::NoBorrows)
+ }
+
+ /// Transition into the next state, [`ParamLifetimeLowerer`].
+ fn into_param_ltl(self, elision_source: ElisionSource) -> ParamLifetimeLowerer<'ast> {
+ ParamLifetimeLowerer {
+ elision_source,
+ base: self.base,
+ }
+ }
+}
+
+impl<'ast> ParamLifetimeLowerer<'ast> {
+ /// Once all lifetimes in the parameters are lowered, this function is
+ /// called to transition to the next state, [`ReturnLifetimeLowerer`].
+ pub fn into_return_ltl(self) -> ReturnLifetimeLowerer<'ast> {
+ ReturnLifetimeLowerer {
+ elision_source: self.elision_source,
+ base: self.base,
+ }
+ }
+}
+
+impl<'ast> LifetimeLowerer for ParamLifetimeLowerer<'ast> {
+ fn lower_lifetime(&mut self, borrow: &ast::Lifetime) -> MaybeStatic<Lifetime> {
+ let lifetime = self.base.lower_lifetime(borrow);
+ self.elision_source.visit_lifetime(lifetime);
+ lifetime
+ }
+
+ fn lower_generics(
+ &mut self,
+ lifetimes: &[ast::Lifetime],
+ type_generics: &ast::LifetimeEnv,
+ is_self: bool,
+ ) -> Lifetimes {
+ if is_self {
+ self.base.self_lifetimes_or_new(lifetimes)
+ } else {
+ self.lower_lifetimes(lifetimes, type_generics)
+ }
+ }
+}
+
+impl<'ast> ReturnLifetimeLowerer<'ast> {
+ /// Finalize the lifetimes in the method, returning the resulting [`LifetimeEnv`].
+ pub fn finish(self) -> LifetimeEnv {
+ LifetimeEnv::new(self.base.nodes, self.base.num_lifetimes)
+ }
+}
+
+impl<'ast> LifetimeLowerer for ReturnLifetimeLowerer<'ast> {
+ fn lower_lifetime(&mut self, borrow: &ast::Lifetime) -> MaybeStatic<Lifetime> {
+ match borrow {
+ ast::Lifetime::Static => MaybeStatic::Static,
+ ast::Lifetime::Named(named) => {
+ MaybeStatic::NonStatic(Lifetime::from_ast(named, self.base.lifetime_env))
+ }
+ ast::Lifetime::Anonymous => match self.elision_source {
+ ElisionSource::SelfParam(lifetime) | ElisionSource::OneParam(lifetime) => lifetime,
+ ElisionSource::NoBorrows => {
+ panic!("nothing to borrow from, this shouldn't pass rustc's checks")
+ }
+ ElisionSource::MultipleBorrows => {
+ panic!("source of elision is ambiguous, this shouldn't pass rustc's checks")
+ }
+ },
+ }
+ }
+
+ fn lower_generics(
+ &mut self,
+ lifetimes: &[ast::Lifetime],
+ type_generics: &ast::LifetimeEnv,
+ is_self: bool,
+ ) -> Lifetimes {
+ if is_self {
+ self.base.self_lifetimes_or_new(lifetimes)
+ } else {
+ self.lower_lifetimes(lifetimes, type_generics)
+ }
+ }
+}
+
+impl LifetimeLowerer for &ast::LifetimeEnv {
+ fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime> {
+ match lifetime {
+ ast::Lifetime::Static => MaybeStatic::Static,
+ ast::Lifetime::Named(named) => MaybeStatic::NonStatic(Lifetime::from_ast(named, self)),
+ ast::Lifetime::Anonymous => {
+ panic!("anonymous lifetime inside struct, this shouldn't pass rustc's checks")
+ }
+ }
+ }
+
+ fn lower_generics(
+ &mut self,
+ lifetimes: &[ast::Lifetime],
+ type_generics: &ast::LifetimeEnv,
+ _: bool,
+ ) -> Lifetimes {
+ self.lower_lifetimes(lifetimes, type_generics)
+ }
+}
+
+// Things to test:
+// 1. ensure that if there are multiple inputs that are `Self`, where `Self` has
+// an elided lifetime, all expansions of `Self` have the same anonymous lifetimes.
+
+#[cfg(test)]
+mod tests {
+ use strck::IntoCk;
+
+ /// Convert a syntax tree into a [`TypeContext`].
+ macro_rules! tcx {
+ ($($tokens:tt)*) => {{
+ let m = crate::ast::Module::from_syn(&syn::parse_quote! { $($tokens)* }, true);
+
+ let mut env = crate::Env::default();
+ let mut top_symbols = crate::ModuleEnv::new(Default::default());
+
+ m.insert_all_types(crate::ast::Path::empty(), &mut env);
+ top_symbols.insert(m.name.clone(), crate::ast::ModSymbol::SubModule(m.name.clone()));
+
+ env.insert(crate::ast::Path::empty(), top_symbols);
+
+ let mut backend = crate::hir::BasicAttributeValidator::new("test-backend");
+ backend.support.static_slices = true;
+
+ // Don't run validation: it will error on elision. We want this code to support
+ // elision even if we don't actually allow it, since good diagnostics involve understanding
+ // broken code.
+ let (_, tcx) = crate::hir::TypeContext::from_ast_without_validation(&env, backend).unwrap();
+
+ tcx
+ }}
+ }
+
+ macro_rules! do_test {
+ ($($tokens:tt)*) => {{
+ let mut settings = insta::Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ let tcx = tcx! { $($tokens)* };
+
+ insta::assert_debug_snapshot!(tcx);
+ })
+ }}
+ }
+
+ #[test]
+ fn simple_mod() {
+ do_test! {
+ mod ffi {
+ #[diplomat::opaque]
+ struct Opaque<'a> {
+ s: DiplomatStrSlice<'a>,
+ }
+
+ struct Struct<'a> {
+ s: DiplomatStrSlice<'a>,
+ }
+
+ #[diplomat::out]
+ struct OutStruct<'a> {
+ inner: Box<Opaque<'a>>,
+ }
+
+ impl<'a> OutStruct<'a> {
+ pub fn new(s: &'a DiplomatStr) -> Self {
+ Self { inner: Box::new(Opaque { s }) }
+ }
+
+ }
+
+ impl<'a> Struct<'a> {
+ pub fn rustc_elision(self, s: &DiplomatStr) -> &DiplomatStr {
+ s
+ }
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn test_elision_in_struct() {
+ let tcx = tcx! {
+ mod ffi {
+ #[diplomat::opaque]
+ struct Opaque;
+
+ #[diplomat::opaque]
+ struct Opaque2<'a>(&'a str);
+
+ impl Opaque {
+ // This should have two elided lifetimes
+ pub fn elided(&self, x: &Opaque2) {
+
+ }
+ }
+ }
+ };
+
+ let method = &tcx
+ .opaques()
+ .iter()
+ .find(|def| def.name == "Opaque")
+ .unwrap()
+ .methods[0];
+
+ assert_eq!(
+ method.lifetime_env.num_lifetimes(),
+ 3,
+ "elided() must have three anon lifetimes"
+ );
+ insta::assert_debug_snapshot!(method);
+ }
+
+ #[test]
+ fn test_borrowing_fields() {
+ use std::collections::BTreeMap;
+ use std::fmt;
+
+ let tcx = tcx! {
+ mod ffi {
+ #[diplomat::opaque]
+ pub struct Opaque;
+
+ struct Input<'p, 'q> {
+ p_data: &'p Opaque,
+ q_data: &'q Opaque,
+ name: DiplomatStrSlice<'static>,
+ inner: Inner<'q>,
+ }
+
+ struct Inner<'a> {
+ more_data: DiplomatStrSlice<'a>,
+ }
+
+ struct Output<'p,'q> {
+ p_data: &'p Opaque,
+ q_data: &'q Opaque,
+ }
+
+ impl<'a, 'b> Input<'a, 'b> {
+ pub fn as_output(self, _s: &'static DiplomatStr) -> Output<'b, 'a> {
+ Output { data: self.data }
+ }
+
+ }
+ }
+ };
+
+ let method = &tcx
+ .structs()
+ .iter()
+ .find(|def| def.name == "Input")
+ .unwrap()
+ .methods[0];
+
+ let visitor = method.borrowing_field_visitor(&tcx, "this".ck().unwrap());
+ let mut lt_to_borrowing_fields: BTreeMap<_, Vec<_>> = BTreeMap::new();
+ visitor.visit_borrowing_fields(|lt, bf| {
+ lt_to_borrowing_fields
+ .entry(lt)
+ .or_default()
+ .push(DebugBorrowingField(bf));
+ });
+
+ struct DebugBorrowingField<'m>(crate::hir::borrowing_field::BorrowingField<'m>);
+
+ impl<'m> fmt::Debug for DebugBorrowingField<'m> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("\"")?;
+ self.0.try_backtrace(|i, ident| {
+ if i != 0 {
+ f.write_str(".")?;
+ }
+ f.write_str(ident.as_str())
+ })?;
+ f.write_str("\"")
+ }
+ }
+
+ let mut settings = insta::Settings::new();
+ settings.set_sort_maps(true);
+
+ settings.bind(|| {
+ insta::assert_debug_snapshot!(lt_to_borrowing_fields);
+ })
+ }
+}
diff --git a/crates/diplomat_core/src/hir/lifetimes.rs b/crates/diplomat_core/src/hir/lifetimes.rs
new file mode 100644
index 0000000..2b461ba
--- /dev/null
+++ b/crates/diplomat_core/src/hir/lifetimes.rs
@@ -0,0 +1,454 @@
+//! Lifetime information for types.
+#![allow(dead_code)]
+
+use super::IdentBuf;
+use crate::ast;
+use core::fmt::Debug;
+use core::hash::Hash;
+
+use smallvec::{smallvec, SmallVec};
+use std::borrow::{Borrow, Cow};
+
+/// Convenience const representing the number of lifetimes a [`LifetimeEnv`]
+/// can hold inline before needing to dynamically allocate.
+pub(crate) const INLINE_NUM_LIFETIMES: usize = 4;
+
+/// The lifetimes and bounds found on a method or type definition
+#[derive(Debug)]
+pub struct LifetimeEnv {
+ /// List of named lifetimes in scope of the method, and their bounds
+ nodes: SmallVec<[BoundedLifetime; INLINE_NUM_LIFETIMES]>,
+
+ /// Only relevant for method LifetimeEnvs (otherwise this is nodes.len())
+ ///
+ /// The number of named _and_ anonymous lifetimes in the method.
+ /// We store the sum since it represents the upper bound on what indices
+ /// are in range of the graph. If we make a [`Lifetimes`] with
+ /// `num_lifetimes` entries, then `Lifetime`s that convert into
+ /// `Lifetime`s will fall into this range, and we'll know that it's
+ /// a named lifetime if it's < `nodes.len()`, or that it's an anonymous
+ /// lifetime if it's < `num_lifetimes`. Otherwise, we'd have to make a
+ /// distinction in `Lifetime` about which context it's in.
+ num_lifetimes: usize,
+}
+
+impl LifetimeEnv {
+ /// Format a lifetime indexing this env for use in code
+ pub fn fmt_lifetime(&self, lt: impl Borrow<Lifetime>) -> Cow<str> {
+ // we use Borrow here so that this can be used in templates where there's autoborrowing
+ let lt = *lt.borrow();
+ if let Some(lt) = self.nodes.get(lt.0) {
+ Cow::from(lt.ident.as_str())
+ } else if lt.0 < self.num_lifetimes {
+ format!("anon_{}", lt.0 - self.nodes.len()).into()
+ } else {
+ panic!("Found out of range lifetime: Got {lt:?} for env with {} nodes and {} total lifetimes", self.nodes.len(), self.num_lifetimes);
+ }
+ }
+
+ /// Get an iterator of all lifetimes that this must live as long as (including itself)
+ /// with the first lifetime always being returned first
+
+ pub fn all_shorter_lifetimes(
+ &self,
+ lt: impl Borrow<Lifetime>,
+ ) -> impl Iterator<Item = Lifetime> + '_ {
+ // we use Borrow here so that this can be used in templates where there's autoborrowing
+ let lt = *lt.borrow();
+ // longer = true, since we are looking for lifetimes this is longer than
+ LifetimeTransitivityIterator::new(self, lt.0, false)
+ }
+
+ /// Same as all_shorter_lifetimes but the other way
+ pub fn all_longer_lifetimes(
+ &self,
+ lt: impl Borrow<Lifetime>,
+ ) -> impl Iterator<Item = Lifetime> + '_ {
+ // we use Borrow here so that this can be used in templates where there's autoborrowing
+ let lt = *lt.borrow();
+ LifetimeTransitivityIterator::new(self, lt.0, true)
+ }
+
+ // List all named and unnamed lifetimes
+ pub fn num_lifetimes(&self) -> usize {
+ self.num_lifetimes
+ }
+
+ pub fn all_lifetimes(&self) -> impl ExactSizeIterator<Item = Lifetime> {
+ (0..self.num_lifetimes()).map(Lifetime::new)
+ }
+
+ /// Get the bounds for a named lifetime (none for unnamed lifetimes)
+ pub(super) fn get_bounds(&self, named_lifetime: Lifetime) -> Option<&BoundedLifetime> {
+ self.nodes.get(named_lifetime.0)
+ }
+
+ /// Returns a new [`LifetimeEnv`].
+ pub(super) fn new(
+ nodes: SmallVec<[BoundedLifetime; INLINE_NUM_LIFETIMES]>,
+ num_lifetimes: usize,
+ ) -> Self {
+ Self {
+ nodes,
+ num_lifetimes,
+ }
+ }
+
+ /// Returns a fresh [`Lifetimes`] corresponding to `self`.
+ pub fn lifetimes(&self) -> Lifetimes {
+ let indices = (0..self.num_lifetimes)
+ .map(|index| MaybeStatic::NonStatic(Lifetime::new(index)))
+ .collect();
+
+ Lifetimes { indices }
+ }
+
+ /// Returns a new [`SubtypeLifetimeVisitor`], which can visit all reachable
+ /// lifetimes
+ pub fn subtype_lifetimes_visitor<F>(&self, visit_fn: F) -> SubtypeLifetimeVisitor<'_, F>
+ where
+ F: FnMut(Lifetime),
+ {
+ SubtypeLifetimeVisitor::new(self, visit_fn)
+ }
+}
+
+/// A lifetime in a [`LifetimeEnv`], which keeps track of which lifetimes it's
+/// longer and shorter than.
+///
+/// Invariant: for a BoundedLifetime found inside a LifetimeEnv, all short/long connections
+/// should be bidirectional.
+#[derive(Debug)]
+pub(super) struct BoundedLifetime {
+ pub(super) ident: IdentBuf,
+ /// Lifetimes longer than this (not transitive)
+ ///
+ /// These are the inverse graph edges compared to `shorter`
+ pub(super) longer: SmallVec<[Lifetime; 2]>,
+ /// Lifetimes this is shorter than (not transitive)
+ ///
+ /// These match `'a: 'b + 'c` bounds
+ pub(super) shorter: SmallVec<[Lifetime; 2]>,
+}
+
+impl BoundedLifetime {
+ /// Returns a new [`BoundedLifetime`].
+ pub(super) fn new(
+ ident: IdentBuf,
+ longer: SmallVec<[Lifetime; 2]>,
+ shorter: SmallVec<[Lifetime; 2]>,
+ ) -> Self {
+ Self {
+ ident,
+ longer,
+ shorter,
+ }
+ }
+}
+
+/// Visit subtype lifetimes recursively, keeping track of which have already
+/// been visited.
+pub struct SubtypeLifetimeVisitor<'lt, F> {
+ lifetime_env: &'lt LifetimeEnv,
+ visited: SmallVec<[bool; INLINE_NUM_LIFETIMES]>,
+ visit_fn: F,
+}
+
+impl<'lt, F> SubtypeLifetimeVisitor<'lt, F>
+where
+ F: FnMut(Lifetime),
+{
+ fn new(lifetime_env: &'lt LifetimeEnv, visit_fn: F) -> Self {
+ Self {
+ lifetime_env,
+ visited: smallvec![false; lifetime_env.nodes.len()],
+ visit_fn,
+ }
+ }
+
+ /// Visit more sublifetimes. This method tracks which lifetimes have already
+ /// been visited, and uses this to not visit the same lifetime twice.
+ pub fn visit_subtypes(&mut self, method_lifetime: Lifetime) {
+ if let Some(visited @ false) = self.visited.get_mut(method_lifetime.0) {
+ *visited = true;
+
+ (self.visit_fn)(method_lifetime);
+
+ for longer in self.lifetime_env.nodes[method_lifetime.0].longer.iter() {
+ self.visit_subtypes(*longer)
+ }
+ } else {
+ debug_assert!(
+ method_lifetime.0 > self.lifetime_env.num_lifetimes,
+ "method lifetime has an internal index that's not in range of the lifetime env"
+ );
+ }
+ }
+}
+
+/// Wrapper type for `Lifetime` and `Lifetime`, indicating that it may
+/// be the `'static` lifetime.
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+#[allow(clippy::exhaustive_enums)] // this will only ever have two variants
+pub enum MaybeStatic<T> {
+ Static,
+ NonStatic(T),
+}
+
+impl<T> MaybeStatic<T> {
+ /// Maps the lifetime, if it's not the `'static` lifetime, to another
+ /// non-static lifetime.
+ pub(super) fn map_nonstatic<F, R>(self, f: F) -> MaybeStatic<R>
+ where
+ F: FnOnce(T) -> R,
+ {
+ match self {
+ MaybeStatic::Static => MaybeStatic::Static,
+ MaybeStatic::NonStatic(lifetime) => MaybeStatic::NonStatic(f(lifetime)),
+ }
+ }
+
+ /// Maps the lifetime, if it's not the `'static` lifetime, to a potentially
+ /// static lifetime.
+ pub(super) fn flat_map_nonstatic<R, F>(self, f: F) -> MaybeStatic<R>
+ where
+ F: FnOnce(T) -> MaybeStatic<R>,
+ {
+ match self {
+ MaybeStatic::Static => MaybeStatic::Static,
+ MaybeStatic::NonStatic(lifetime) => f(lifetime),
+ }
+ }
+}
+
+/// A lifetime that exists as part of a type name, struct signature, or method signature.
+///
+/// This index only makes sense in the context of a surrounding type or method; since
+/// this is essentially an index into that type/method's lifetime list.
+#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub struct Lifetime(usize);
+
+/// A set of lifetimes found on a type name, struct signature, or method signature
+#[derive(Clone, Debug)]
+pub struct Lifetimes {
+ indices: SmallVec<[MaybeStatic<Lifetime>; 2]>,
+}
+
+impl Lifetime {
+ pub(super) const fn new(index: usize) -> Self {
+ Self(index)
+ }
+}
+
+impl Lifetimes {
+ /// Returns an iterator over the contained [`Lifetime`]s.
+ pub fn lifetimes(&self) -> impl ExactSizeIterator<Item = MaybeStatic<Lifetime>> + '_ {
+ self.indices.iter().copied()
+ }
+
+ pub(super) fn as_slice(&self) -> &[MaybeStatic<Lifetime>] {
+ self.indices.as_slice()
+ }
+}
+
+impl Lifetime {
+ /// Returns a [`Lifetime`] from its AST counterparts.
+ pub(super) fn from_ast(named: &ast::NamedLifetime, lifetime_env: &ast::LifetimeEnv) -> Self {
+ let index = lifetime_env
+ .id(named)
+ .unwrap_or_else(|| panic!("lifetime `{named}` not found in lifetime env"));
+ Self::new(index)
+ }
+
+ /// Returns a new [`MaybeStatic<Lifetime>`] representing `self` in the
+ /// scope of the method that it appears in.
+ ///
+ /// For example, if we have some `Foo<'a>` type with a field `&'a Bar`, then
+ /// we can call this on the `'a` on the field. If `Foo` was `Foo<'static>`
+ /// in the method, then this will return `MaybeStatic::Static`. But if it
+ /// was `Foo<'b>`, then this will return `MaybeStatic::NonStatic` containing
+ /// the `Lifetime` corresponding to `'b`.
+ pub fn as_method_lifetime(self, method_lifetimes: &Lifetimes) -> MaybeStatic<Lifetime> {
+ method_lifetimes.indices[self.0]
+ }
+}
+
+impl Lifetimes {
+ pub(super) fn from_fn<F>(lifetimes: &[ast::Lifetime], lower_fn: F) -> Self
+ where
+ F: FnMut(&ast::Lifetime) -> MaybeStatic<Lifetime>,
+ {
+ Self {
+ indices: lifetimes.iter().map(lower_fn).collect(),
+ }
+ }
+
+ /// Append an additional lifetime. Used to tack on anon lifetimes
+ pub(super) fn append_lifetime(&mut self, lifetime: MaybeStatic<Lifetime>) {
+ self.indices.push(lifetime)
+ }
+
+ /// Returns a new [`Lifetimes`] representing the lifetimes in the scope
+ /// of the method this type appears in.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # struct Alice<'a>(&'a ());
+ /// # struct Bob<'b>(&'b ());
+ /// struct Foo<'a, 'b> {
+ /// alice: Alice<'a>,
+ /// bob: Bob<'b>,
+ /// }
+ ///
+ /// fn bar<'x, 'y>(arg: Foo<'x, 'y>) {}
+ /// ```
+ /// Here, `Foo` will have a [`Lifetimes`] containing `['a, 'b]`,
+ /// and `bar` will have a [`Lifetimes`] containing `{'x: 'x, 'y: 'y}`.
+ /// When we enter the scope of `Foo` as a type, we use this method to combine
+ /// the two to get a new [`Lifetimes`] representing the mapping from
+ /// lifetimes in `Foo`'s scope to lifetimes in `bar`s scope: `{'a: 'x, 'b: 'y}`.
+ ///
+ /// This tells us that `arg.alice` has lifetime `'x` in the method, and
+ /// that `arg.bob` has lifetime `'y`.
+ pub fn as_method_lifetimes(&self, method_lifetimes: &Lifetimes) -> Lifetimes {
+ let indices = self
+ .indices
+ .iter()
+ .map(|maybe_static_lt| {
+ maybe_static_lt.flat_map_nonstatic(|lt| lt.as_method_lifetime(method_lifetimes))
+ })
+ .collect();
+
+ Lifetimes { indices }
+ }
+}
+
+struct LifetimeTransitivityIterator<'env> {
+ env: &'env LifetimeEnv,
+ visited: Vec<bool>,
+ queue: Vec<usize>,
+ longer: bool,
+}
+
+impl<'env> LifetimeTransitivityIterator<'env> {
+ // Longer is whether we are looking for lifetimes longer or shorter than this
+ fn new(env: &'env LifetimeEnv, starting: usize, longer: bool) -> Self {
+ Self {
+ env,
+ visited: vec![false; env.num_lifetimes()],
+ queue: vec![starting],
+ longer,
+ }
+ }
+}
+
+impl<'env> Iterator for LifetimeTransitivityIterator<'env> {
+ type Item = Lifetime;
+
+ fn next(&mut self) -> Option<Lifetime> {
+ while let Some(next) = self.queue.pop() {
+ if self.visited[next] {
+ continue;
+ }
+ self.visited[next] = true;
+
+ if let Some(named) = self.env.nodes.get(next) {
+ let edge_dir = if self.longer {
+ &named.longer
+ } else {
+ &named.shorter
+ };
+ self.queue.extend(edge_dir.iter().map(|i| i.0));
+ }
+
+ return Some(Lifetime::new(next));
+ }
+ None
+ }
+}
+
+/// Convenience type for linking the lifetimes found at a type *use* site (e.g. `&'c Foo<'a, 'b>`)
+/// with the lifetimes found at its *def* site (e.g. `struct Foo<'x, 'y>`).
+///
+/// Construct this by calling `.linked_lifetimes()` on a StructPath or OpaquePath
+pub struct LinkedLifetimes<'def, 'tcx> {
+ env: &'tcx LifetimeEnv,
+ self_lt: Option<MaybeStatic<Lifetime>>,
+ lifetimes: &'def Lifetimes,
+}
+
+impl<'def, 'tcx> LinkedLifetimes<'def, 'tcx> {
+ pub(crate) fn new(
+ env: &'tcx LifetimeEnv,
+ self_lt: Option<MaybeStatic<Lifetime>>,
+ lifetimes: &'def Lifetimes,
+ ) -> Self {
+ debug_assert_eq!(
+ lifetimes.lifetimes().len(),
+ env.all_lifetimes().len(),
+ "Should only link lifetimes between a type and its def"
+ );
+ Self {
+ env,
+ self_lt,
+ lifetimes,
+ }
+ }
+
+ /// Takes a lifetime at the def site and produces one at the use site
+ pub fn def_to_use(&self, def_lt: Lifetime) -> MaybeStatic<Lifetime> {
+ *self
+ .lifetimes
+ .as_slice()
+ .get(def_lt.0)
+ .expect("All def site lifetimes must be used!")
+ }
+
+ /// The lifetime env at the def site. Def lifetimes should be resolved
+ /// against this.
+ pub fn def_env(&self) -> &'tcx LifetimeEnv {
+ self.env
+ }
+
+ /// Link lifetimes from the use site to lifetimes from the def site, only including
+ /// lifetimes found at the def site.
+ ///
+ /// This will *not* include the self-lifetime, i.e. for an opaque use site `&'c Foo<'a, 'b>`
+ /// this will not include `'c` (but you can obtain it from [`Self::self_lifetime()`]))
+ ///
+ /// The return iterator returns pairs of (use_lt, def_lt), in order.
+ ///
+ /// This behaves identically to [`Self::lifetimes_all()`] for `LinkedLifetimes` constructed
+ /// from anything other than a borrowing opaque.
+ pub fn lifetimes_def_only(
+ &self,
+ ) -> impl Iterator<Item = (MaybeStatic<Lifetime>, Lifetime)> + '_ {
+ self.lifetimes.lifetimes().zip(self.env.all_lifetimes())
+ }
+
+ /// If there is a self-lifetime (e.g. `'c` on `&'c Foo<'a, 'b>`), return it. This lifetime
+ /// isn't found at the def site.
+ pub fn self_lifetime(&self) -> Option<MaybeStatic<Lifetime>> {
+ self.self_lt
+ }
+
+ /// Link lifetimes from the use site to lifetimes from the def site, including self lifetimes.
+ ///
+ /// This returns Options since self-lifetimes do not map to anything at the def site.
+ ///
+ /// The return iterator returns pairs of (use_lt, def_lt), in order, with the first entry potentially being
+ /// the self lifetime (which has a def_lt of None).
+ ///
+ /// This behaves identically to [`Self::lifetimes_all()`] for `LinkedLifetimes` constructed
+ /// from anything other than a borrowing opaque.
+ pub fn lifetimes_all(
+ &self,
+ ) -> impl Iterator<Item = (MaybeStatic<Lifetime>, Option<Lifetime>)> + '_ {
+ self.self_lt.iter().map(|i| (*i, None)).chain(
+ self.lifetimes
+ .lifetimes()
+ .zip(self.env.all_lifetimes().map(Some)),
+ )
+ }
+}
diff --git a/crates/diplomat_core/src/hir/lowering.rs b/crates/diplomat_core/src/hir/lowering.rs
new file mode 100644
index 0000000..f312eb0
--- /dev/null
+++ b/crates/diplomat_core/src/hir/lowering.rs
@@ -0,0 +1,1584 @@
+use super::{
+ AttributeContext, AttributeValidator, Attrs, Borrow, BoundedLifetime, Callback, CallbackParam,
+ EnumDef, EnumPath, EnumVariant, Everywhere, IdentBuf, InputOnly, IntType, Lifetime,
+ LifetimeEnv, LifetimeLowerer, LookupId, MaybeOwn, Method, NonOptional, OpaqueDef, OpaquePath,
+ Optional, OutStructDef, OutStructField, OutStructPath, OutType, Param, ParamLifetimeLowerer,
+ ParamSelf, PrimitiveType, ReturnLifetimeLowerer, ReturnType, ReturnableStructPath,
+ SelfParamLifetimeLowerer, SelfType, Slice, SpecialMethod, SpecialMethodPresence, StructDef,
+ StructField, StructPath, SuccessType, SymbolId, TraitDef, TraitParamSelf, TraitPath,
+ TyPosition, Type, TypeDef, TypeId,
+};
+use crate::ast::attrs::AttrInheritContext;
+use crate::{ast, Env};
+use core::fmt;
+use strck::IntoCk;
+
+/// An error from lowering the AST to the HIR.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum LoweringError {
+ /// The purpose of having this is that translating to the HIR has enormous
+ /// potential for really detailed error handling and giving suggestions.
+ ///
+ /// Unfortunately, working out what the error enum should look like to enable
+ /// this is really hard. The plan is that once the lowering code is completely
+ /// written, we ctrl+F for `"LoweringError::Other"` in the lowering code, and turn every
+ /// instance into an specialized enum variant, generalizing where possible
+ /// without losing any information.
+ Other(String),
+}
+
+impl fmt::Display for LoweringError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Self::Other(ref s) => s.fmt(f),
+ }
+ }
+}
+
+#[derive(Default, Clone)]
+pub struct ErrorContext {
+ item: String,
+ subitem: Option<String>,
+}
+
+impl fmt::Display for ErrorContext {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(ref subitem) = self.subitem {
+ write!(f, "{}::{subitem}", self.item)
+ } else {
+ self.item.fmt(f)
+ }
+ }
+}
+
+impl fmt::Debug for ErrorContext {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(self, f)
+ }
+}
+
+/// An error store, which one can push errors to. It keeps track of the
+/// current "context" for the error, usually a type or a type::method. `'tree`
+/// is the AST/HIR tree it is temporarily borrowing from for the context.
+#[derive(Default)]
+pub struct ErrorStore<'tree> {
+ /// The errors
+ errors: Vec<ErrorAndContext>,
+ /// The current context (types, modules)
+ item: &'tree str,
+ /// The current sub-item context (methods, etc)
+ subitem: Option<&'tree str>,
+}
+
+pub type ErrorAndContext = (ErrorContext, LoweringError);
+
+impl<'tree> ErrorStore<'tree> {
+ /// Push an error to the error store
+ pub fn push(&mut self, error: LoweringError) {
+ let context = ErrorContext {
+ item: self.item.into(),
+ subitem: self.subitem.map(|s| s.into()),
+ };
+ self.errors.push((context, error));
+ }
+
+ pub(super) fn take_errors(&mut self) -> Vec<ErrorAndContext> {
+ core::mem::take(&mut self.errors)
+ }
+
+ pub(super) fn is_empty(&self) -> bool {
+ self.errors.is_empty()
+ }
+
+ pub(super) fn set_item(&mut self, item: &'tree str) {
+ self.item = item;
+ self.subitem = None;
+ }
+ pub(super) fn set_subitem(&mut self, subitem: &'tree str) {
+ self.subitem = Some(subitem);
+ }
+}
+
+pub(super) struct LoweringContext<'ast> {
+ pub lookup_id: LookupId<'ast>,
+ pub errors: ErrorStore<'ast>,
+ pub env: &'ast Env,
+ pub attr_validator: Box<dyn AttributeValidator>,
+}
+
+/// An item and the info needed to
+pub(crate) struct ItemAndInfo<'ast, Ast> {
+ pub(crate) item: &'ast Ast,
+ pub(crate) in_path: &'ast ast::Path,
+ /// Any parent attributes resolved from the module, for a type context
+ pub(crate) ty_parent_attrs: Attrs,
+
+ /// Any parent attributes resolved from the module, for a method context
+ pub(crate) method_parent_attrs: Attrs,
+ pub(crate) id: SymbolId,
+}
+
+impl<'ast> LoweringContext<'ast> {
+ /// Lowers an [`ast::Ident`]s into an [`hir::IdentBuf`].
+ ///
+ /// If there are any errors, they're pushed to `errors` and `Err` is returned.
+ pub(super) fn lower_ident(
+ &mut self,
+ ident: &ast::Ident,
+ context: &'static str,
+ ) -> Result<IdentBuf, ()> {
+ match ident.as_str().ck() {
+ Ok(name) => Ok(name.to_owned()),
+ Err(e) => {
+ self.errors.push(LoweringError::Other(format!(
+ "Ident `{ident}` from {context} could not be turned into a Rust ident: {e}"
+ )));
+ Err(())
+ }
+ }
+ }
+
+ /// Lowers multiple items at once
+ fn lower_all<Ast: 'static, Hir>(
+ &mut self,
+ ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, Ast>>,
+ lower: impl Fn(&mut Self, ItemAndInfo<'ast, Ast>) -> Result<Hir, ()>,
+ ) -> Result<Vec<Hir>, ()> {
+ let mut hir_types = Ok(Vec::with_capacity(ast_defs.len()));
+
+ for def in ast_defs {
+ let hir_type = lower(self, def);
+
+ match (hir_type, &mut hir_types) {
+ (Ok(hir_type), Ok(hir_types)) => hir_types.push(hir_type),
+ _ => hir_types = Err(()),
+ }
+ }
+
+ hir_types
+ }
+
+ pub(super) fn lower_all_enums(
+ &mut self,
+ ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::Enum>>,
+ ) -> Result<Vec<EnumDef>, ()> {
+ self.lower_all(ast_defs, Self::lower_enum)
+ }
+ pub(super) fn lower_all_structs(
+ &mut self,
+ ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::Struct>>,
+ ) -> Result<Vec<StructDef>, ()> {
+ self.lower_all(ast_defs, Self::lower_struct)
+ }
+ pub(super) fn lower_all_out_structs(
+ &mut self,
+ ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::Struct>>,
+ ) -> Result<Vec<OutStructDef>, ()> {
+ self.lower_all(ast_defs, Self::lower_out_struct)
+ }
+ pub(super) fn lower_all_opaques(
+ &mut self,
+ ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::OpaqueStruct>>,
+ ) -> Result<Vec<OpaqueDef>, ()> {
+ self.lower_all(ast_defs, Self::lower_opaque)
+ }
+ pub(super) fn lower_all_traits(
+ &mut self,
+ ast_defs: impl ExactSizeIterator<Item = ItemAndInfo<'ast, ast::Trait>>,
+ ) -> Result<Vec<TraitDef>, ()> {
+ self.lower_all(ast_defs, Self::lower_trait)
+ }
+
+ fn lower_enum(&mut self, item: ItemAndInfo<'ast, ast::Enum>) -> Result<EnumDef, ()> {
+ let ast_enum = item.item;
+ self.errors.set_item(ast_enum.name.as_str());
+ let name = self.lower_ident(&ast_enum.name, "enum name");
+ let attrs = self.attr_validator.attr_from_ast(
+ &ast_enum.attrs,
+ &item.ty_parent_attrs,
+ &mut self.errors,
+ );
+
+ let mut variants = Ok(Vec::with_capacity(ast_enum.variants.len()));
+ let variant_parent_attrs = attrs.for_inheritance(AttrInheritContext::Variant);
+ for (ident, discriminant, docs, attrs) in ast_enum.variants.iter() {
+ let name = self.lower_ident(ident, "enum variant");
+ let attrs =
+ self.attr_validator
+ .attr_from_ast(attrs, &variant_parent_attrs, &mut self.errors);
+ match (name, &mut variants) {
+ (Ok(name), Ok(variants)) => {
+ let variant = EnumVariant {
+ docs: docs.clone(),
+ name,
+ discriminant: *discriminant,
+ attrs,
+ };
+ self.attr_validator.validate(
+ &variant.attrs,
+ AttributeContext::EnumVariant(&variant),
+ &mut self.errors,
+ );
+ variants.push(variant);
+ }
+ _ => variants = Err(()),
+ }
+ }
+
+ let mut special_method_presence = SpecialMethodPresence::default();
+ let methods = if attrs.disable {
+ Vec::new()
+ } else {
+ self.lower_all_methods(
+ &ast_enum.methods[..],
+ item.in_path,
+ &item.method_parent_attrs,
+ item.id.try_into()?,
+ &mut special_method_presence,
+ )?
+ };
+
+ let def = EnumDef::new(
+ ast_enum.docs.clone(),
+ name?,
+ variants?,
+ methods,
+ attrs,
+ special_method_presence,
+ );
+
+ self.attr_validator.validate(
+ &def.attrs,
+ AttributeContext::Type(TypeDef::from(&def)),
+ &mut self.errors,
+ );
+
+ Ok(def)
+ }
+
+ fn lower_opaque(
+ &mut self,
+ item: ItemAndInfo<'ast, ast::OpaqueStruct>,
+ ) -> Result<OpaqueDef, ()> {
+ let ast_opaque = item.item;
+ self.errors.set_item(ast_opaque.name.as_str());
+ let name = self.lower_ident(&ast_opaque.name, "opaque name");
+ let dtor_abi_name = self.lower_ident(&ast_opaque.dtor_abi_name, "opaque dtor abi name");
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &ast_opaque.attrs,
+ &item.ty_parent_attrs,
+ &mut self.errors,
+ );
+ let mut special_method_presence = SpecialMethodPresence::default();
+ let methods = if attrs.disable {
+ Vec::new()
+ } else {
+ self.lower_all_methods(
+ &ast_opaque.methods[..],
+ item.in_path,
+ &item.method_parent_attrs,
+ item.id.try_into()?,
+ &mut special_method_presence,
+ )?
+ };
+ let lifetimes = self.lower_type_lifetime_env(&ast_opaque.lifetimes);
+
+ let def = OpaqueDef::new(
+ ast_opaque.docs.clone(),
+ name?,
+ methods,
+ attrs,
+ lifetimes?,
+ special_method_presence,
+ dtor_abi_name?,
+ );
+ self.attr_validator.validate(
+ &def.attrs,
+ AttributeContext::Type(TypeDef::from(&def)),
+ &mut self.errors,
+ );
+ Ok(def)
+ }
+
+ fn lower_struct(&mut self, item: ItemAndInfo<'ast, ast::Struct>) -> Result<StructDef, ()> {
+ let ast_struct = item.item;
+ self.errors.set_item(ast_struct.name.as_str());
+ let struct_name = self.lower_ident(&ast_struct.name, "struct name")?;
+
+ let mut fields = Ok(Vec::with_capacity(ast_struct.fields.len()));
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &ast_struct.attrs,
+ &item.ty_parent_attrs,
+ &mut self.errors,
+ );
+ // Only compute fields if the type isn't disabled, otherwise we may encounter forbidden types
+ if !attrs.disable {
+ for (name, ty, docs, attrs) in ast_struct.fields.iter() {
+ let name = self.lower_ident(name, "struct field name")?;
+ if !ty.is_ffi_safe() {
+ let ffisafe = ty.ffi_safe_version();
+ self.errors.push(LoweringError::Other(format!(
+ "Found FFI-unsafe type {ty} in struct field {struct_name}.{name}, consider using {ffisafe}",
+ )));
+ }
+ let ty = self.lower_type::<Everywhere>(
+ ty,
+ &mut &ast_struct.lifetimes,
+ false,
+ item.in_path,
+ );
+
+ let field_attrs =
+ self.attr_validator
+ .attr_from_ast(attrs, &Attrs::default(), &mut self.errors);
+
+ self.attr_validator.validate(
+ &field_attrs,
+ AttributeContext::Field,
+ &mut self.errors,
+ );
+
+ match (ty, &mut fields) {
+ (Ok(ty), Ok(fields)) => fields.push(StructField {
+ docs: docs.clone(),
+ name,
+ ty,
+ attrs: field_attrs,
+ }),
+ _ => fields = Err(()),
+ }
+ }
+ }
+ let lifetimes = self.lower_type_lifetime_env(&ast_struct.lifetimes);
+
+ let mut special_method_presence = SpecialMethodPresence::default();
+ let methods = if attrs.disable {
+ Vec::new()
+ } else if ast_struct.fields.is_empty() {
+ if !ast_struct.methods.is_empty() {
+ self.errors.push(LoweringError::Other(format!(
+ "Methods on ZST structs are not yet implemented: {}",
+ ast_struct.name
+ )));
+ return Err(());
+ } else {
+ Vec::new()
+ }
+ } else {
+ self.lower_all_methods(
+ &ast_struct.methods[..],
+ item.in_path,
+ &item.method_parent_attrs,
+ item.id.try_into()?,
+ &mut special_method_presence,
+ )?
+ };
+ let def = StructDef::new(
+ ast_struct.docs.clone(),
+ struct_name,
+ fields?,
+ methods,
+ attrs,
+ lifetimes?,
+ special_method_presence,
+ );
+
+ self.attr_validator.validate(
+ &def.attrs,
+ AttributeContext::Type(TypeDef::from(&def)),
+ &mut self.errors,
+ );
+ Ok(def)
+ }
+
+ fn lower_trait(&mut self, item: ItemAndInfo<'ast, ast::Trait>) -> Result<TraitDef, ()> {
+ let ast_trait = item.item;
+ self.errors.set_item(ast_trait.name.as_str());
+ let trait_name = self.lower_ident(&ast_trait.name, "trait name")?;
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &ast_trait.attrs,
+ &item.ty_parent_attrs,
+ &mut self.errors,
+ );
+
+ let fcts = if attrs.disable {
+ Vec::new()
+ } else {
+ let mut fcts = Vec::with_capacity(ast_trait.methods.len());
+ for ast_trait_method in ast_trait.methods.iter() {
+ fcts.push(self.lower_trait_method(ast_trait_method, item.in_path, &attrs)?);
+ }
+ fcts
+ };
+ let lifetimes = self.lower_type_lifetime_env(&ast_trait.lifetimes);
+ let def = TraitDef::new(ast_trait.docs.clone(), trait_name, fcts, attrs, lifetimes?);
+
+ self.attr_validator
+ .validate(&def.attrs, AttributeContext::Trait(&def), &mut self.errors);
+ Ok(def)
+ }
+
+ fn lower_trait_method(
+ &mut self,
+ ast_trait_method: &'ast ast::TraitMethod,
+ in_path: &ast::Path,
+ parent_trait_attrs: &Attrs,
+ ) -> Result<Callback, ()> {
+ self.errors.set_subitem(ast_trait_method.name.as_str());
+ let name = ast_trait_method.name.clone();
+ let self_param_ltl = SelfParamLifetimeLowerer::new(&ast_trait_method.lifetimes, self)?;
+ let (param_self, mut param_ltl) =
+ if let Some(self_param) = ast_trait_method.self_param.as_ref() {
+ let (param_self, param_ltl) =
+ self.lower_trait_self_param(self_param, self_param_ltl, in_path)?;
+ (Some(param_self), param_ltl)
+ } else {
+ (None, SelfParamLifetimeLowerer::no_self_ref(self_param_ltl))
+ };
+
+ let params =
+ self.lower_many_callback_params(&ast_trait_method.params, &mut param_ltl, in_path)?;
+
+ let output = if let Some(out_ty) = &ast_trait_method.output_type {
+ Some(self.lower_type(out_ty, &mut param_ltl, false /* in_struct */, in_path)?)
+ } else {
+ None
+ };
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &ast_trait_method.attrs,
+ parent_trait_attrs,
+ &mut self.errors,
+ );
+
+ Ok(Callback {
+ param_self,
+ params,
+ output: Box::new(output),
+ name: Some(self.lower_ident(&name, "trait name")?),
+ attrs: Some(attrs),
+ docs: Some(ast_trait_method.docs.clone()),
+ })
+ }
+
+ fn lower_out_struct(
+ &mut self,
+ item: ItemAndInfo<'ast, ast::Struct>,
+ ) -> Result<OutStructDef, ()> {
+ let ast_out_struct = item.item;
+ self.errors.set_item(ast_out_struct.name.as_str());
+ let name = self.lower_ident(&ast_out_struct.name, "out-struct name");
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &ast_out_struct.attrs,
+ &item.ty_parent_attrs,
+ &mut self.errors,
+ );
+ let fields = if ast_out_struct.fields.is_empty() {
+ self.errors.push(LoweringError::Other(format!(
+ "struct `{}` is a ZST because it has no fields",
+ ast_out_struct.name
+ )));
+ Err(())
+ } else {
+ let mut fields = Ok(Vec::with_capacity(ast_out_struct.fields.len()));
+ // Only compute fields if the type isn't disabled, otherwise we may encounter forbidden types
+ if !attrs.disable {
+ for (name, ty, docs, attrs) in ast_out_struct.fields.iter() {
+ let name = self.lower_ident(name, "out-struct field name");
+ let ty = self.lower_out_type(
+ ty,
+ &mut &ast_out_struct.lifetimes,
+ item.in_path,
+ true,
+ false,
+ );
+
+ match (name, ty, &mut fields) {
+ (Ok(name), Ok(ty), Ok(fields)) => fields.push(OutStructField {
+ docs: docs.clone(),
+ name,
+ ty,
+ attrs: self.attr_validator.attr_from_ast(
+ attrs,
+ &Attrs::default(),
+ &mut self.errors,
+ ),
+ }),
+ _ => fields = Err(()),
+ }
+ }
+ }
+
+ fields
+ };
+ let mut special_method_presence = SpecialMethodPresence::default();
+ let methods = if attrs.disable {
+ Vec::new()
+ } else {
+ self.lower_all_methods(
+ &ast_out_struct.methods[..],
+ item.in_path,
+ &item.method_parent_attrs,
+ item.id.try_into()?,
+ &mut special_method_presence,
+ )?
+ };
+
+ let lifetimes = self.lower_type_lifetime_env(&ast_out_struct.lifetimes);
+ let def = OutStructDef::new(
+ ast_out_struct.docs.clone(),
+ name?,
+ fields?,
+ methods,
+ attrs,
+ lifetimes?,
+ special_method_presence,
+ );
+
+ self.attr_validator.validate(
+ &def.attrs,
+ AttributeContext::Type(TypeDef::from(&def)),
+ &mut self.errors,
+ );
+ Ok(def)
+ }
+
+ /// Lowers an [`ast::Method`]s an [`hir::Method`].
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ fn lower_method(
+ &mut self,
+ method: &'ast ast::Method,
+ in_path: &ast::Path,
+ attrs: Attrs,
+ self_id: TypeId,
+ special_method_presence: &mut SpecialMethodPresence,
+ ) -> Result<Method, ()> {
+ let name = self.lower_ident(&method.name, "method name");
+
+ let (ast_params, takes_write) = match method.params.split_last() {
+ Some((last, remaining)) if last.is_write() => (remaining, true),
+ _ => (&method.params[..], false),
+ };
+
+ let self_param_ltl = SelfParamLifetimeLowerer::new(&method.lifetime_env, self)?;
+
+ let (param_self, param_ltl) = if let Some(self_param) = method.self_param.as_ref() {
+ let (param_self, param_ltl) =
+ self.lower_self_param(self_param, self_param_ltl, &method.abi_name, in_path)?;
+ (Some(param_self), param_ltl)
+ } else {
+ (None, SelfParamLifetimeLowerer::no_self_ref(self_param_ltl))
+ };
+
+ let (params, return_ltl) = self.lower_many_params(ast_params, param_ltl, in_path)?;
+
+ let (output, lifetime_env) = self.lower_return_type(
+ method.return_type.as_ref(),
+ takes_write,
+ return_ltl,
+ in_path,
+ )?;
+
+ let abi_name = self.lower_ident(&method.abi_name, "method abi name")?;
+ let hir_method = Method {
+ docs: method.docs.clone(),
+ name: name?,
+ abi_name,
+ lifetime_env,
+ param_self,
+ params,
+ output,
+ attrs,
+ };
+
+ self.attr_validator.validate(
+ &hir_method.attrs,
+ AttributeContext::Method(&hir_method, self_id, special_method_presence),
+ &mut self.errors,
+ );
+
+ let is_comparison = matches!(
+ hir_method.attrs.special_method,
+ Some(SpecialMethod::Comparison)
+ );
+ if is_comparison && method.return_type != Some(ast::TypeName::Ordering) {
+ self.errors.push(LoweringError::Other(
+ "Found comparison method that does not return cmp::Ordering".into(),
+ ));
+ return Err(());
+ }
+
+ Ok(hir_method)
+ }
+
+ /// Lowers many [`ast::Method`]s into a vector of [`hir::Method`]s.
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ fn lower_all_methods(
+ &mut self,
+ ast_methods: &'ast [ast::Method],
+ in_path: &ast::Path,
+ method_parent_attrs: &Attrs,
+ self_id: TypeId,
+ special_method_presence: &mut SpecialMethodPresence,
+ ) -> Result<Vec<Method>, ()> {
+ let mut methods = Ok(Vec::with_capacity(ast_methods.len()));
+
+ for method in ast_methods {
+ self.errors.set_subitem(method.name.as_str());
+ let attrs = self.attr_validator.attr_from_ast(
+ &method.attrs,
+ method_parent_attrs,
+ &mut self.errors,
+ );
+ if attrs.disable {
+ continue;
+ }
+ let method =
+ self.lower_method(method, in_path, attrs, self_id, special_method_presence);
+ match (method, &mut methods) {
+ (Ok(method), Ok(methods)) => {
+ methods.push(method);
+ }
+ _ => methods = Err(()),
+ }
+ }
+
+ methods
+ }
+
+ /// Lowers an [`ast::TypeName`]s into a [`hir::Type`] (for non-output types)
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ fn lower_type<P: TyPosition<StructPath = StructPath, OpaqueOwnership = Borrow>>(
+ &mut self,
+ ty: &ast::TypeName,
+ ltl: &mut impl LifetimeLowerer,
+ in_struct: bool,
+ in_path: &ast::Path,
+ ) -> Result<Type<P>, ()> {
+ match ty {
+ ast::TypeName::Primitive(prim) => Ok(Type::Primitive(PrimitiveType::from_ast(*prim))),
+ ast::TypeName::Ordering => {
+ self.errors.push(LoweringError::Other("Found cmp::Ordering in parameter or struct field, it is only allowed in return types".to_string()));
+ Err(())
+ }
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => match path
+ .resolve(in_path, self.env)
+ {
+ ast::CustomType::Struct(strct) => {
+ if strct.fields.is_empty() {
+ self.errors.push(LoweringError::Other(format!(
+ "zero-size types are not allowed as method arguments: {ty} in {path}"
+ )));
+ return Err(());
+ }
+ if let Some(tcx_id) = self.lookup_id.resolve_struct(strct) {
+ let lifetimes =
+ ltl.lower_generics(&path.lifetimes[..], &strct.lifetimes, ty.is_self());
+
+ Ok(Type::Struct(StructPath::new(lifetimes, tcx_id)))
+ } else if self.lookup_id.resolve_out_struct(strct).is_some() {
+ self.errors.push(LoweringError::Other(format!("found struct in input that is marked with #[diplomat::out]: {ty} in {path}")));
+ Err(())
+ } else {
+ unreachable!("struct `{}` wasn't found in the set of structs or out-structs, this is a bug.", strct.name);
+ }
+ }
+ ast::CustomType::Opaque(_) => {
+ self.errors.push(LoweringError::Other(format!(
+ "Opaque passed by value: {path}"
+ )));
+ Err(())
+ }
+ ast::CustomType::Enum(enm) => {
+ let tcx_id = self
+ .lookup_id
+ .resolve_enum(enm)
+ .expect("can't find enum in lookup map, which contains all enums from env");
+
+ Ok(Type::Enum(EnumPath::new(tcx_id)))
+ }
+ },
+ ast::TypeName::ImplTrait(path) => {
+ if !self.attr_validator.attrs_supported().traits {
+ self.errors.push(LoweringError::Other(
+ "Traits are not supported by this backend".into(),
+ ));
+ }
+ let trt = path.resolve_trait(in_path, self.env);
+ let tcx_id = self
+ .lookup_id
+ .resolve_trait(&trt)
+ .expect("can't find trait in lookup map, which contains all traits from env");
+ let lifetimes =
+ ltl.lower_generics(&path.lifetimes[..], &trt.lifetimes, ty.is_self());
+
+ Ok(Type::ImplTrait(P::build_trait_path(TraitPath::new(
+ lifetimes, tcx_id,
+ ))))
+ }
+ ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref() {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(opaque) => {
+ let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
+ let lifetimes = ltl.lower_generics(
+ &path.lifetimes[..],
+ &opaque.lifetimes,
+ ref_ty.is_self(),
+ );
+ let tcx_id = self.lookup_id.resolve_opaque(opaque).expect(
+ "can't find opaque in lookup map, which contains all opaques from env",
+ );
+
+ Ok(Type::Opaque(OpaquePath::new(
+ lifetimes,
+ Optional(false),
+ borrow,
+ tcx_id,
+ )))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found &T in input where T is a custom type, but not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ }
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found &T in input where T isn't a custom type and therefore not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ },
+ ast::TypeName::Box(box_ty) => {
+ self.errors.push(match box_ty.as_ref() {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(_) => LoweringError::Other(format!("found Box<T> in input where T is an opaque, but owned opaques aren't allowed in inputs. try &T instead? T = {path}")),
+ _ => LoweringError::Other(format!("found Box<T> in input where T is a custom type but not opaque. non-opaques can't be behind pointers, and opaques in inputs can't be owned. T = {path}")),
+ }
+ }
+ _ => LoweringError::Other(format!("found Box<T> in input where T isn't a custom type. T = {box_ty}")),
+ });
+ Err(())
+ }
+ ast::TypeName::Option(opt_ty, stdlib) => {
+ match opt_ty.as_ref() {
+ ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref()
+ {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => match path
+ .resolve(in_path, self.env)
+ {
+ ast::CustomType::Opaque(opaque) => {
+ if *stdlib == ast::StdlibOrDiplomat::Diplomat {
+ self.errors.push(LoweringError::Other("found DiplomatOption<&T>, please use Option<&T> (DiplomatOption is for primitives, structs, and enums)".to_string()));
+ return Err(());
+ }
+ let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
+ let lifetimes = ltl.lower_generics(
+ &path.lifetimes,
+ &opaque.lifetimes,
+ ref_ty.is_self(),
+ );
+ let tcx_id = self.lookup_id.resolve_opaque(opaque).expect(
+ "can't find opaque in lookup map, which contains all opaques from env",
+ );
+
+ Ok(Type::Opaque(OpaquePath::new(
+ lifetimes,
+ Optional(true),
+ borrow,
+ tcx_id,
+ )))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<&T> in input where T is a custom type, but it's not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ },
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<&T> in input, but T isn't a custom type and therefore not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ },
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(_) => {
+ self.errors.push(LoweringError::Other("Found Option<T> where T is opaque, opaque types must be behind a reference".into()));
+ Err(())
+ }
+ _ => {
+ if in_struct && *stdlib == ast::StdlibOrDiplomat::Stdlib {
+ self.errors.push(LoweringError::Other("Found Option<T> for struct/enum T in a struct field, please use DiplomatOption<T>".into()));
+ return Err(());
+ }
+ if !self.attr_validator.attrs_supported().option {
+ self.errors.push(LoweringError::Other("Options of structs/enums/primitives not supported by this backend".into()));
+ }
+ let inner = self.lower_type(opt_ty, ltl, in_struct, in_path)?;
+ Ok(Type::DiplomatOption(Box::new(inner)))
+ }
+ }
+ }
+ ast::TypeName::Primitive(prim) => {
+ if in_struct && *stdlib == ast::StdlibOrDiplomat::Stdlib {
+ self.errors.push(LoweringError::Other("Found Option<T> for primitive T in a struct field, please use DiplomatOption<T>".into()));
+ return Err(());
+ }
+ if !self.attr_validator.attrs_supported().option {
+ self.errors.push(LoweringError::Other(
+ "Options of structs/enums/primitives not supported by this backend"
+ .into(),
+ ));
+ }
+ Ok(Type::DiplomatOption(Box::new(Type::Primitive(
+ PrimitiveType::from_ast(*prim),
+ ))))
+ }
+ ast::TypeName::Box(box_ty) => {
+ // we could see whats in the box here too
+ self.errors.push(LoweringError::Other(format!("found Option<Box<T>> in input, but box isn't allowed in inputs. T = {box_ty}")));
+ Err(())
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<T> in input, where T isn't a reference but Option<T> in inputs requires that T is a reference to an opaque. T = {opt_ty}")));
+ Err(())
+ }
+ }
+ }
+ ast::TypeName::Result(_, _, _) => {
+ self.errors.push(LoweringError::Other(
+ "Results can only appear as the top-level return type of methods".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::Write => {
+ self.errors.push(LoweringError::Other(
+ "DiplomatWrite can only appear as the last parameter of a method".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::StrReference(lifetime, encoding, _stdlib) => {
+ let new_lifetime = lifetime.as_ref().map(|lt| ltl.lower_lifetime(lt));
+ if let Some(super::MaybeStatic::Static) = new_lifetime {
+ if !self.attr_validator.attrs_supported().static_slices {
+ self.errors.push(LoweringError::Other(
+ "'static string slice types are not supported. Try #[diplomat::attr(not(supports = static_slices), disable)]".into()
+ ));
+ }
+ }
+ Ok(Type::Slice(Slice::Str(new_lifetime, *encoding)))
+ }
+ ast::TypeName::StrSlice(encoding, _stdlib) => Ok(Type::Slice(Slice::Strs(*encoding))),
+ ast::TypeName::PrimitiveSlice(lm, prim, _stdlib) => {
+ let new_lifetime = lm
+ .as_ref()
+ .map(|(lt, m)| Borrow::new(ltl.lower_lifetime(lt), *m));
+
+ if let Some(b) = new_lifetime {
+ if let super::MaybeStatic::Static = b.lifetime {
+ if !self.attr_validator.attrs_supported().static_slices {
+ self.errors.push(LoweringError::Other(
+ format!("'static {prim:?} slice types not supported. Try #[diplomat::attr(not(supports = static_slices), disable)]")
+ ));
+ }
+ }
+ }
+
+ Ok(Type::Slice(Slice::Primitive(
+ new_lifetime,
+ PrimitiveType::from_ast(*prim),
+ )))
+ }
+ ast::TypeName::Function(input_types, out_type) => {
+ if !self.attr_validator.attrs_supported().callbacks {
+ self.errors.push(LoweringError::Other(
+ "Callback arguments are not supported by this backend".into(),
+ ));
+ }
+ if in_struct {
+ self.errors.push(LoweringError::Other(
+ "Callbacks currently unsupported in structs".into(),
+ ));
+ return Err(());
+ }
+ let mut params: Vec<CallbackParam> = Vec::new();
+ for in_ty in input_types.iter() {
+ let hir_in_ty = self
+ .lower_out_type(in_ty, ltl, in_path, false, false)
+ .unwrap();
+ if hir_in_ty.lifetimes().next().is_some() {
+ self.errors.push(LoweringError::Other("Callback parameters can't be borrowed, and therefore can't have lifetimes".into()));
+ return Err(());
+ }
+ params.push(CallbackParam {
+ ty: hir_in_ty,
+ name: None,
+ })
+ }
+ Ok(Type::Callback(P::build_callback(Callback {
+ param_self: None,
+ params,
+ output: Box::new(match **out_type {
+ ast::TypeName::Unit => None,
+ _ => Some(self.lower_type(out_type, ltl, in_struct, in_path)?),
+ }),
+ name: None,
+ attrs: None,
+ docs: None,
+ })))
+ }
+ ast::TypeName::Unit => {
+ self.errors.push(LoweringError::Other("Unit types can only appear as the return value of a method, or as the Ok/Err variants of a returned result".into()));
+ Err(())
+ }
+ }
+ }
+
+ /// Lowers an [`ast::TypeName`]s into an [`hir::OutType`].
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ fn lower_out_type(
+ &mut self,
+ ty: &ast::TypeName,
+ ltl: &mut impl LifetimeLowerer,
+ in_path: &ast::Path,
+ in_struct: bool,
+ in_result_option: bool,
+ ) -> Result<OutType, ()> {
+ match ty {
+ ast::TypeName::Primitive(prim) => {
+ Ok(OutType::Primitive(PrimitiveType::from_ast(*prim)))
+ }
+ ast::TypeName::Ordering => {
+ if in_struct {
+ self.errors.push(LoweringError::Other(
+ "Found cmp::Ordering in struct field, it is only allowed in return types"
+ .to_string(),
+ ));
+ Err(())
+ } else {
+ Ok(Type::Primitive(PrimitiveType::Int(IntType::I8)))
+ }
+ }
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Struct(strct) => {
+ if !in_result_option && strct.fields.is_empty() {
+ self.errors.push(LoweringError::Other(format!("Found zero-size struct outside a `Result` or `Option`: {ty} in {in_path}")));
+ return Err(());
+ }
+ let lifetimes =
+ ltl.lower_generics(&path.lifetimes, &strct.lifetimes, ty.is_self());
+
+ if let Some(tcx_id) = self.lookup_id.resolve_struct(strct) {
+ Ok(OutType::Struct(ReturnableStructPath::Struct(
+ StructPath::new(lifetimes, tcx_id),
+ )))
+ } else if let Some(tcx_id) = self.lookup_id.resolve_out_struct(strct) {
+ Ok(OutType::Struct(ReturnableStructPath::OutStruct(
+ OutStructPath::new(lifetimes, tcx_id),
+ )))
+ } else {
+ unreachable!("struct `{}` wasn't found in the set of structs or out-structs, this is a bug.", strct.name);
+ }
+ }
+ ast::CustomType::Opaque(_) => {
+ self.errors.push(LoweringError::Other(format!(
+ "Opaque passed by value in input: {path}"
+ )));
+ Err(())
+ }
+ ast::CustomType::Enum(enm) => {
+ let tcx_id = self.lookup_id.resolve_enum(enm).expect(
+ "can't find enum in lookup map, which contains all enums from env",
+ );
+
+ Ok(OutType::Enum(EnumPath::new(tcx_id)))
+ }
+ }
+ }
+ ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref() {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(opaque) => {
+ let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
+ let lifetimes = ltl.lower_generics(
+ &path.lifetimes,
+ &opaque.lifetimes,
+ ref_ty.is_self(),
+ );
+ let tcx_id = self.lookup_id.resolve_opaque(opaque).expect(
+ "can't find opaque in lookup map, which contains all opaques from env",
+ );
+
+ Ok(OutType::Opaque(OpaquePath::new(
+ lifetimes,
+ Optional(false),
+ MaybeOwn::Borrow(borrow),
+ tcx_id,
+ )))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found &T in output where T is a custom type, but not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ }
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found &T in output where T isn't a custom type and therefore not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ },
+ ast::TypeName::Box(box_ty) => match box_ty.as_ref() {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(opaque) => {
+ let lifetimes = ltl.lower_generics(
+ &path.lifetimes,
+ &opaque.lifetimes,
+ box_ty.is_self(),
+ );
+ let tcx_id = self.lookup_id.resolve_opaque(opaque).expect(
+ "can't find opaque in lookup map, which contains all opaques from env",
+ );
+
+ Ok(OutType::Opaque(OpaquePath::new(
+ lifetimes,
+ Optional(false),
+ MaybeOwn::Own,
+ tcx_id,
+ )))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Box<T> in output where T is a custom type but not opaque. non-opaques can't be behind pointers. T = {path}")));
+ Err(())
+ }
+ }
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!(
+ "found Box<T> in output where T isn't a custom type. T = {box_ty}"
+ )));
+ Err(())
+ }
+ },
+ ast::TypeName::Option(opt_ty, stdlib) => match opt_ty.as_ref() {
+ ast::TypeName::Reference(lifetime, mutability, ref_ty) => match ref_ty.as_ref() {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(opaque) => {
+ if *stdlib == ast::StdlibOrDiplomat::Diplomat {
+ self.errors.push(LoweringError::Other("found DiplomatOption<&T>, please use Option<&T> (DiplomatOption is for primitives, structs, and enums)".to_string()));
+ return Err(());
+ }
+ let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
+ let lifetimes = ltl.lower_generics(
+ &path.lifetimes,
+ &opaque.lifetimes,
+ ref_ty.is_self(),
+ );
+ let tcx_id = self.lookup_id.resolve_opaque(opaque).expect(
+ "can't find opaque in lookup map, which contains all opaques from env",
+ );
+
+ Ok(OutType::Opaque(OpaquePath::new(
+ lifetimes,
+ Optional(true),
+ MaybeOwn::Borrow(borrow),
+ tcx_id,
+ )))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<&T> where T is a custom type, but it's not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ }
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<&T>, but T isn't a custom type and therefore not opaque. T = {ref_ty}")));
+ Err(())
+ }
+ },
+ ast::TypeName::Box(box_ty) => match box_ty.as_ref() {
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(opaque) => {
+ if *stdlib == ast::StdlibOrDiplomat::Diplomat {
+ self.errors.push(LoweringError::Other("found DiplomatOption<Box<T>>, please use Option<Box<T>> (DiplomatOption is for primitives, structs, and enums)".to_string()));
+ return Err(());
+ }
+ let lifetimes = ltl.lower_generics(
+ &path.lifetimes,
+ &opaque.lifetimes,
+ box_ty.is_self(),
+ );
+ let tcx_id = self.lookup_id.resolve_opaque(opaque).expect(
+ "can't find opaque in lookup map, which contains all opaques from env",
+ );
+
+ Ok(OutType::Opaque(OpaquePath::new(
+ lifetimes,
+ Optional(true),
+ MaybeOwn::Own,
+ tcx_id,
+ )))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<Box<T>> where T is a custom type, but it's not opaque. T = {box_ty}")));
+ Err(())
+ }
+ }
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<Box<T>>, but T isn't a custom type and therefore not opaque. T = {box_ty}")));
+ Err(())
+ }
+ },
+ ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
+ match path.resolve(in_path, self.env) {
+ ast::CustomType::Opaque(_) => {
+ self.errors.push(LoweringError::Other("Found Option<T> where T is opaque, opaque types must be behind a reference".into()));
+ Err(())
+ }
+ _ => {
+ if in_struct && *stdlib == ast::StdlibOrDiplomat::Stdlib {
+ self.errors.push(LoweringError::Other("Found Option<T> for struct/enum T in a struct field, please use DiplomatOption<T>".into()));
+ return Err(());
+ }
+ if !self.attr_validator.attrs_supported().option {
+ self.errors.push(LoweringError::Other("Options of structs/enums/primitives not supported by this backend".into()));
+ }
+ let inner =
+ self.lower_out_type(opt_ty, ltl, in_path, in_struct, true)?;
+ Ok(Type::DiplomatOption(Box::new(inner)))
+ }
+ }
+ }
+ ast::TypeName::Primitive(prim) => {
+ if in_struct && *stdlib == ast::StdlibOrDiplomat::Stdlib {
+ self.errors.push(LoweringError::Other("Found Option<T> for primitive T in a struct field, please use DiplomatOption<T>".into()));
+ return Err(());
+ }
+ if !self.attr_validator.attrs_supported().option {
+ self.errors.push(LoweringError::Other(
+ "Options of structs/enums/primitives not supported by this backend"
+ .into(),
+ ));
+ }
+ Ok(Type::DiplomatOption(Box::new(Type::Primitive(
+ PrimitiveType::from_ast(*prim),
+ ))))
+ }
+ _ => {
+ self.errors.push(LoweringError::Other(format!("found Option<T>, where T isn't a reference but Option<T> requires that T is a reference to an opaque. T = {opt_ty}")));
+ Err(())
+ }
+ },
+ ast::TypeName::Result(_, _, _) => {
+ self.errors.push(LoweringError::Other(
+ "Results can only appear as the top-level return type of methods".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::Write => {
+ self.errors.push(LoweringError::Other(
+ "DiplomatWrite can only appear as the last parameter of a method".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::PrimitiveSlice(None, _, _stdlib)
+ | ast::TypeName::StrReference(None, _, _stdlib) => {
+ self.errors.push(LoweringError::Other(
+ "Owned slices cannot be returned".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::StrReference(Some(l), encoding, _stdlib) => Ok(OutType::Slice(
+ Slice::Str(Some(ltl.lower_lifetime(l)), *encoding),
+ )),
+ ast::TypeName::StrSlice(.., _stdlib) => {
+ self.errors.push(LoweringError::Other(
+ "String slices can only be an input type".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::PrimitiveSlice(Some((lt, m)), prim, _stdlib) => {
+ Ok(OutType::Slice(Slice::Primitive(
+ Some(Borrow::new(ltl.lower_lifetime(lt), *m)),
+ PrimitiveType::from_ast(*prim),
+ )))
+ }
+ ast::TypeName::Unit => {
+ self.errors.push(LoweringError::Other("Unit types can only appear as the return value of a method, or as the Ok/Err variants of a returned result".into()));
+ Err(())
+ }
+ ast::TypeName::Function(_, _) => {
+ self.errors.push(LoweringError::Other(
+ "Function types can only be an input type".into(),
+ ));
+ Err(())
+ }
+ ast::TypeName::ImplTrait(_) => {
+ self.errors.push(LoweringError::Other(
+ "Trait impls can only be an input type".into(),
+ ));
+ Err(())
+ }
+ }
+ }
+
+ /// Lowers an [`ast::SelfParam`] into an [`hir::ParamSelf`].
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ fn lower_self_param(
+ &mut self,
+ self_param: &ast::SelfParam,
+ self_param_ltl: SelfParamLifetimeLowerer<'ast>,
+ method_full_path: &ast::Ident, // for better error msg
+ in_path: &ast::Path,
+ ) -> Result<(ParamSelf, ParamLifetimeLowerer<'ast>), ()> {
+ match self_param.path_type.resolve(in_path, self.env) {
+ ast::CustomType::Struct(strct) => {
+ if let Some(tcx_id) = self.lookup_id.resolve_struct(strct) {
+ if self_param.reference.is_some() {
+ self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes a reference to a struct as a self parameter, which isn't allowed")));
+ Err(())
+ } else {
+ let mut param_ltl = self_param_ltl.no_self_ref();
+
+ // Even if we explicitly write out the type of `self` like
+ // `self: Foo<'a>`, the `'a` is still not considered for
+ // elision according to rustc, so is_self=true.
+ let type_lifetimes = param_ltl.lower_generics(
+ &self_param.path_type.lifetimes[..],
+ &strct.lifetimes,
+ true,
+ );
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &self_param.attrs,
+ &Attrs::default(),
+ &mut self.errors,
+ );
+
+ self.attr_validator.validate(
+ &attrs,
+ AttributeContext::SelfParam,
+ &mut self.errors,
+ );
+
+ Ok((
+ ParamSelf::new(
+ SelfType::Struct(StructPath::new(type_lifetimes, tcx_id)),
+ attrs,
+ ),
+ param_ltl,
+ ))
+ }
+ } else if self.lookup_id.resolve_out_struct(strct).is_some() {
+ if let Some((lifetime, _)) = &self_param.reference {
+ self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an out-struct as the self parameter, which isn't allowed. Also, it's behind a reference, `{lifetime}`, but only opaques can be behind references")));
+ Err(())
+ } else {
+ self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an out-struct as the self parameter, which isn't allowed")));
+ Err(())
+ }
+ } else {
+ unreachable!(
+ "struct `{}` wasn't found in the set of structs or out-structs, this is a bug.",
+ strct.name
+ );
+ }
+ }
+ ast::CustomType::Opaque(opaque) => {
+ let tcx_id = self
+ .lookup_id
+ .resolve_opaque(opaque)
+ .expect("opaque is in env");
+
+ if let Some((lifetime, mutability)) = &self_param.reference {
+ let (borrow_lifetime, mut param_ltl) = self_param_ltl.lower_self_ref(lifetime);
+ let borrow = Borrow::new(borrow_lifetime, *mutability);
+ let lifetimes = param_ltl.lower_generics(
+ &self_param.path_type.lifetimes,
+ &opaque.lifetimes,
+ true,
+ );
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &self_param.attrs,
+ &Attrs::default(),
+ &mut self.errors,
+ );
+
+ self.attr_validator.validate(
+ &attrs,
+ AttributeContext::SelfParam,
+ &mut self.errors,
+ );
+
+ Ok((
+ ParamSelf::new(
+ SelfType::Opaque(OpaquePath::new(
+ lifetimes,
+ NonOptional,
+ borrow,
+ tcx_id,
+ )),
+ attrs,
+ ),
+ param_ltl,
+ ))
+ } else {
+ self.errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs")));
+ Err(())
+ }
+ }
+ ast::CustomType::Enum(enm) => {
+ let tcx_id = self.lookup_id.resolve_enum(enm).expect("enum is in env");
+
+ let attrs = self.attr_validator.attr_from_ast(
+ &self_param.attrs,
+ &Attrs::default(),
+ &mut self.errors,
+ );
+
+ self.attr_validator
+ .validate(&attrs, AttributeContext::SelfParam, &mut self.errors);
+
+ Ok((
+ ParamSelf::new(SelfType::Enum(EnumPath::new(tcx_id)), attrs),
+ self_param_ltl.no_self_ref(),
+ ))
+ }
+ }
+ }
+
+ fn lower_trait_self_param(
+ &mut self,
+ self_param: &ast::TraitSelfParam,
+ self_param_ltl: SelfParamLifetimeLowerer<'ast>,
+ in_path: &ast::Path,
+ ) -> Result<(TraitParamSelf, ParamLifetimeLowerer<'ast>), ()> {
+ let trt = self_param.path_trait.resolve_trait(in_path, self.env);
+ if let Some(tcx_id) = self.lookup_id.resolve_trait(&trt) {
+ // check this -- I think we should be able to have both self and non-self
+ if let Some((lifetime, _)) = &self_param.reference {
+ let (_, mut param_ltl) = self_param_ltl.lower_self_ref(lifetime);
+ let lifetimes = param_ltl.lower_generics(
+ &self_param.path_trait.lifetimes,
+ &trt.lifetimes,
+ true,
+ );
+
+ Ok((
+ TraitParamSelf::new(TraitPath::new(lifetimes, tcx_id)),
+ param_ltl,
+ ))
+ } else {
+ let mut param_ltl = self_param_ltl.no_self_ref();
+
+ let type_lifetimes = param_ltl.lower_generics(
+ &self_param.path_trait.lifetimes[..],
+ &trt.lifetimes,
+ true,
+ );
+
+ Ok((
+ TraitParamSelf::new(TraitPath::new(type_lifetimes, tcx_id)),
+ param_ltl,
+ ))
+ }
+ } else {
+ unreachable!(
+ "Trait `{}` wasn't found in the set of traits; this is a bug.",
+ trt.name
+ );
+ }
+ }
+
+ /// Lowers an [`ast::Param`] into an [`hir::Param`].
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ ///
+ /// Note that this expects that if there was a DiplomatWrite param at the end in
+ /// the method, it's not passed into here.
+ fn lower_param(
+ &mut self,
+ param: &ast::Param,
+ ltl: &mut impl LifetimeLowerer,
+ in_path: &ast::Path,
+ ) -> Result<Param, ()> {
+ let name = self.lower_ident(¶m.name, "param name");
+ let ty = self.lower_type::<InputOnly>(¶m.ty, ltl, false, in_path);
+
+ // No parent attrs because parameters do not have a strictly clear parent.
+ let attrs =
+ self.attr_validator
+ .attr_from_ast(¶m.attrs, &Attrs::default(), &mut self.errors);
+
+ self.attr_validator
+ .validate(&attrs, AttributeContext::Param, &mut self.errors);
+
+ Ok(Param::new(name?, ty?, attrs))
+ }
+
+ /// Lowers many [`ast::Param`]s into a vector of [`hir::Param`]s.
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ ///
+ /// Note that this expects that if there was a DiplomatWrite param at the end in
+ /// the method, `ast_params` was sliced to not include it. This happens in
+ /// `self.lower_method`, the caller of this function.
+ fn lower_many_params(
+ &mut self,
+ ast_params: &[ast::Param],
+ mut param_ltl: ParamLifetimeLowerer<'ast>,
+ in_path: &ast::Path,
+ ) -> Result<(Vec<Param>, ReturnLifetimeLowerer<'ast>), ()> {
+ let mut params = Ok(Vec::with_capacity(ast_params.len()));
+
+ for param in ast_params {
+ let param = self.lower_param(param, &mut param_ltl, in_path);
+
+ match (param, &mut params) {
+ (Ok(param), Ok(params)) => {
+ params.push(param);
+ }
+ _ => params = Err(()),
+ }
+ }
+
+ Ok((params?, param_ltl.into_return_ltl()))
+ }
+
+ fn lower_callback_param(
+ &mut self,
+ param: &ast::Param,
+ ltl: &mut impl LifetimeLowerer,
+ in_path: &ast::Path,
+ ) -> Result<CallbackParam, ()> {
+ let name = self.lower_ident(¶m.name, "param name")?;
+ let ty = self.lower_out_type(
+ ¶m.ty, ltl, in_path, false, /* in_struct */
+ false, /* in_result_option */
+ )?;
+
+ Ok(CallbackParam {
+ name: Some(name),
+ ty,
+ })
+ }
+
+ fn lower_many_callback_params(
+ &mut self,
+ ast_params: &[ast::Param],
+ param_ltl: &mut ParamLifetimeLowerer<'ast>,
+ in_path: &ast::Path,
+ ) -> Result<Vec<CallbackParam>, ()> {
+ let mut params = Ok(Vec::with_capacity(ast_params.len()));
+
+ for param in ast_params {
+ let param = self.lower_callback_param(param, param_ltl, in_path);
+
+ match (param, &mut params) {
+ (Ok(param), Ok(params)) => {
+ params.push(param);
+ }
+ _ => params = Err(()),
+ }
+ }
+ params
+ }
+
+ /// Lowers the return type of an [`ast::Method`] into a [`hir::ReturnFallability`].
+ ///
+ /// If there are any errors, they're pushed to `errors` and `None` is returned.
+ fn lower_return_type(
+ &mut self,
+ return_type: Option<&ast::TypeName>,
+ takes_write: bool,
+ mut return_ltl: ReturnLifetimeLowerer<'_>,
+ in_path: &ast::Path,
+ ) -> Result<(ReturnType, LifetimeEnv), ()> {
+ let write_or_unit = if takes_write {
+ SuccessType::Write
+ } else {
+ SuccessType::Unit
+ };
+ match return_type.unwrap_or(&ast::TypeName::Unit) {
+ ast::TypeName::Result(ok_ty, err_ty, _) => {
+ let ok_ty = match ok_ty.as_ref() {
+ ast::TypeName::Unit => Ok(write_or_unit),
+ ty => self
+ .lower_out_type(ty, &mut return_ltl, in_path, false, true)
+ .map(SuccessType::OutType),
+ };
+ let err_ty = match err_ty.as_ref() {
+ ast::TypeName::Unit => Ok(None),
+ ty => self
+ .lower_out_type(ty, &mut return_ltl, in_path, false, true)
+ .map(Some),
+ };
+
+ match (ok_ty, err_ty) {
+ (Ok(ok_ty), Ok(err_ty)) => Ok(ReturnType::Fallible(ok_ty, err_ty)),
+ _ => Err(()),
+ }
+ }
+ ty @ ast::TypeName::Option(value_ty, _stdlib) => match &**value_ty {
+ ast::TypeName::Box(..) | ast::TypeName::Reference(..) => self
+ .lower_out_type(ty, &mut return_ltl, in_path, false, true)
+ .map(SuccessType::OutType)
+ .map(ReturnType::Infallible),
+ ast::TypeName::Unit => Ok(ReturnType::Nullable(write_or_unit)),
+ _ => self
+ .lower_out_type(value_ty, &mut return_ltl, in_path, false, true)
+ .map(SuccessType::OutType)
+ .map(ReturnType::Nullable),
+ },
+ ast::TypeName::Unit => Ok(ReturnType::Infallible(write_or_unit)),
+ ty => self
+ .lower_out_type(ty, &mut return_ltl, in_path, false, false)
+ .map(|ty| ReturnType::Infallible(SuccessType::OutType(ty))),
+ }
+ .map(|r_ty| (r_ty, return_ltl.finish()))
+ }
+
+ fn lower_named_lifetime(
+ &mut self,
+ lifetime: &ast::lifetimes::LifetimeNode,
+ ) -> Result<BoundedLifetime, ()> {
+ Ok(BoundedLifetime {
+ ident: self.lower_ident(lifetime.lifetime.name(), "lifetime")?,
+ longer: lifetime.longer.iter().copied().map(Lifetime::new).collect(),
+ shorter: lifetime
+ .shorter
+ .iter()
+ .copied()
+ .map(Lifetime::new)
+ .collect(),
+ })
+ }
+
+ /// Lowers a lifetime env found on a type
+ ///
+ /// Should not be extended to return LifetimeEnv<Method>, which needs to use the lifetime
+ /// lowerers to handle elision.
+ fn lower_type_lifetime_env(&mut self, ast: &ast::LifetimeEnv) -> Result<LifetimeEnv, ()> {
+ let nodes = ast
+ .nodes
+ .iter()
+ .map(|lt| self.lower_named_lifetime(lt))
+ .collect::<Result<_, ()>>()?;
+
+ Ok(LifetimeEnv::new(nodes, ast.nodes.len()))
+ }
+}
diff --git a/crates/diplomat_core/src/hir/methods.rs b/crates/diplomat_core/src/hir/methods.rs
new file mode 100644
index 0000000..d4b35dd
--- /dev/null
+++ b/crates/diplomat_core/src/hir/methods.rs
@@ -0,0 +1,304 @@
+//! Methods for types and navigating lifetimes within methods.
+
+use std::collections::BTreeSet;
+use std::ops::Deref;
+
+use super::{
+ Attrs, Docs, Ident, IdentBuf, InputOnly, OutType, OutputOnly, SelfType, TraitPath, Type,
+ TypeContext,
+};
+
+use super::lifetimes::{Lifetime, LifetimeEnv, Lifetimes, MaybeStatic};
+
+use borrowing_field::BorrowingFieldVisitor;
+use borrowing_param::BorrowingParamVisitor;
+
+pub mod borrowing_field;
+pub mod borrowing_param;
+
+/// A method exposed to Diplomat.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Method {
+ /// Documentation specified on the method
+ pub docs: Docs,
+ /// The name of the method as initially declared.
+ pub name: IdentBuf,
+ /// The name of the generated `extern "C"` function
+ pub abi_name: IdentBuf,
+ /// The lifetimes introduced in this method and surrounding impl block.
+ pub lifetime_env: LifetimeEnv,
+
+ /// An &self, &mut self, or Self parameter
+ pub param_self: Option<ParamSelf>,
+ /// The parameters of the method
+ pub params: Vec<Param>,
+ /// The output type, including whether it returns a Result/Option/Writeable/etc
+ pub output: ReturnType,
+ /// Resolved (and inherited) diplomat::attr attributes on this method
+ pub attrs: Attrs,
+}
+
+pub trait CallbackInstantiationFunctionality {
+ #[allow(clippy::result_unit_err)]
+ fn get_inputs(&self) -> Result<&[CallbackParam], ()>; // the types of the parameters
+ #[allow(clippy::result_unit_err)]
+ fn get_output_type(&self) -> Result<&Option<Type>, ()>;
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+// Note: we do not support borrowing across callbacks
+pub struct Callback {
+ pub param_self: Option<TraitParamSelf>, // this is None for callbacks as method arguments
+ pub params: Vec<CallbackParam>,
+ pub output: Box<Option<Type>>, // this will be used in Rust (note: can technically be a callback, or void)
+ pub name: Option<IdentBuf>,
+ pub attrs: Option<Attrs>,
+ pub docs: Option<Docs>,
+}
+
+// uninstantiatable; represents no callback allowed
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum NoCallback {}
+
+impl CallbackInstantiationFunctionality for Callback {
+ fn get_inputs(&self) -> Result<&[CallbackParam], ()> {
+ Ok(&self.params)
+ }
+ fn get_output_type(&self) -> Result<&Option<Type>, ()> {
+ Ok(&self.output)
+ }
+}
+
+impl CallbackInstantiationFunctionality for NoCallback {
+ fn get_inputs(&self) -> Result<&[CallbackParam], ()> {
+ Err(())
+ }
+ fn get_output_type(&self) -> Result<&Option<Type>, ()> {
+ Err(())
+ }
+}
+
+/// Type that the method returns.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum SuccessType {
+ /// Conceptually returns a string, which gets written to the `write: DiplomatWrite` argument
+ Write,
+ /// A Diplomat type. Some types can be outputs, but not inputs, which is expressed by the `OutType` parameter.
+ OutType(OutType),
+ /// A `()` type in Rust.
+ Unit,
+}
+
+/// Whether or not the method returns a value or a result.
+#[derive(Debug)]
+#[allow(clippy::exhaustive_enums)] // this only exists for fallible/infallible, breaking changes for more complex returns are ok
+pub enum ReturnType {
+ Infallible(SuccessType),
+ Fallible(SuccessType, Option<OutType>),
+ Nullable(SuccessType),
+}
+
+/// The `self` parameter of a method.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct ParamSelf {
+ pub ty: SelfType,
+ pub attrs: Attrs,
+}
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct TraitParamSelf {
+ pub trait_path: TraitPath,
+}
+
+/// A parameter in a method.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Param {
+ pub name: IdentBuf,
+ pub ty: Type<InputOnly>,
+ pub attrs: Attrs,
+}
+
+/// A parameter in a callback
+/// No name, since all we get is the callback type signature
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct CallbackParam {
+ pub ty: Type<OutputOnly>,
+ pub name: Option<IdentBuf>,
+}
+
+impl SuccessType {
+ /// Returns whether the variant is `Write`.
+ pub fn is_write(&self) -> bool {
+ matches!(self, SuccessType::Write)
+ }
+
+ /// Returns whether the variant is `Unit`.
+ pub fn is_unit(&self) -> bool {
+ matches!(self, SuccessType::Unit)
+ }
+
+ pub fn as_type(&self) -> Option<&OutType> {
+ match self {
+ SuccessType::OutType(ty) => Some(ty),
+ _ => None,
+ }
+ }
+}
+
+impl Deref for ReturnType {
+ type Target = SuccessType;
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ ReturnType::Infallible(ret) | ReturnType::Fallible(ret, _) | Self::Nullable(ret) => ret,
+ }
+ }
+}
+
+impl ReturnType {
+ /// Returns `true` if the FFI function returns `void`. Not that this is different from `is_unit`,
+ /// which will be true for `DiplomatResult<(), E>` and false for infallible write.
+ pub fn is_ffi_unit(&self) -> bool {
+ matches!(
+ self,
+ ReturnType::Infallible(SuccessType::Unit | SuccessType::Write)
+ )
+ }
+
+ /// The "main" return type of this function: the Ok, Some, or regular type
+ pub fn success_type(&self) -> &SuccessType {
+ match &self {
+ Self::Infallible(s) => s,
+ Self::Fallible(s, _) => s,
+ Self::Nullable(s) => s,
+ }
+ }
+
+ /// Get the list of method lifetimes actually used by the method return type
+ ///
+ /// Most input lifetimes aren't actually used. An input lifetime is generated
+ /// for each borrowing parameter but is only important if we use it in the return.
+ pub fn used_method_lifetimes(&self) -> BTreeSet<Lifetime> {
+ let mut set = BTreeSet::new();
+
+ let mut add_to_set = |ty: &OutType| {
+ for lt in ty.lifetimes() {
+ if let MaybeStatic::NonStatic(lt) = lt {
+ set.insert(lt);
+ }
+ }
+ };
+
+ match self {
+ ReturnType::Infallible(SuccessType::OutType(ref ty))
+ | ReturnType::Nullable(SuccessType::OutType(ref ty)) => add_to_set(ty),
+ ReturnType::Fallible(ref ok, ref err) => {
+ if let SuccessType::OutType(ref ty) = ok {
+ add_to_set(ty)
+ }
+ if let Some(ref ty) = err {
+ add_to_set(ty)
+ }
+ }
+ _ => (),
+ }
+
+ set
+ }
+
+ pub fn with_contained_types(&self, mut f: impl FnMut(&OutType)) {
+ match self {
+ Self::Infallible(SuccessType::OutType(o))
+ | Self::Nullable(SuccessType::OutType(o))
+ | Self::Fallible(SuccessType::OutType(o), None) => f(o),
+ Self::Fallible(SuccessType::OutType(o), Some(o2)) => {
+ f(o);
+ f(o2)
+ }
+ Self::Fallible(_, Some(o)) => f(o),
+ _ => (),
+ }
+ }
+}
+
+impl ParamSelf {
+ pub(super) fn new(ty: SelfType, attrs: Attrs) -> Self {
+ Self { ty, attrs }
+ }
+
+ /// Return the number of fields and leaves that will show up in the [`BorrowingFieldVisitor`].
+ ///
+ /// This method is used to calculate how much space to allocate upfront.
+ fn field_leaf_lifetime_counts(&self, tcx: &TypeContext) -> (usize, usize) {
+ match self.ty {
+ SelfType::Opaque(_) => (1, 1),
+ SelfType::Struct(ref ty) => ty.resolve(tcx).fields.iter().fold((1, 0), |acc, field| {
+ let inner = field.ty.field_leaf_lifetime_counts(tcx);
+ (acc.0 + inner.0, acc.1 + inner.1)
+ }),
+ SelfType::Enum(_) => (0, 0),
+ }
+ }
+}
+
+impl TraitParamSelf {
+ pub(super) fn new(trait_path: TraitPath) -> Self {
+ Self { trait_path }
+ }
+}
+
+impl Param {
+ pub(super) fn new(name: IdentBuf, ty: Type<InputOnly>, attrs: Attrs) -> Self {
+ Self { name, ty, attrs }
+ }
+}
+
+impl Method {
+ /// Returns a fresh [`Lifetimes`] corresponding to `self`.
+ pub fn method_lifetimes(&self) -> Lifetimes {
+ self.lifetime_env.lifetimes()
+ }
+
+ /// Returns a new [`BorrowingParamVisitor`], which can *shallowly* link output lifetimes
+ /// to the parameters they borrow from.
+ ///
+ /// This is useful for backends which wish to have lifetime codegen for methods only handle the local
+ /// method lifetime, and delegate to generated code on structs for handling the internals of struct lifetimes.
+ pub fn borrowing_param_visitor<'tcx>(
+ &'tcx self,
+ tcx: &'tcx TypeContext,
+ ) -> BorrowingParamVisitor<'tcx> {
+ BorrowingParamVisitor::new(self, tcx)
+ }
+
+ /// Returns a new [`BorrowingFieldVisitor`], which allocates memory to
+ /// efficiently represent all fields (and their paths!) of the inputs that
+ /// have a lifetime.
+ ///
+ /// This is useful for backends which wish to "splat out" lifetime edge codegen for methods,
+ /// linking each borrowed input param/field (however deep it may be in a struct) to a borrowed output param/field.
+ ///
+ /// ```ignore
+ /// # use std::collections::BTreeMap;
+ /// let visitor = method.borrowing_field_visitor(&tcx, "this".ck().unwrap());
+ /// let mut map = BTreeMap::new();
+ /// visitor.visit_borrowing_fields(|lifetime, field| {
+ /// map.entry(lifetime).or_default().push(field);
+ /// })
+ /// ```
+ pub fn borrowing_field_visitor<'m>(
+ &'m self,
+ tcx: &'m TypeContext,
+ self_name: &'m Ident,
+ ) -> BorrowingFieldVisitor<'m> {
+ BorrowingFieldVisitor::new(self, tcx, self_name)
+ }
+}
diff --git a/crates/diplomat_core/src/hir/methods/borrowing_field.rs b/crates/diplomat_core/src/hir/methods/borrowing_field.rs
new file mode 100644
index 0000000..d03ba5c
--- /dev/null
+++ b/crates/diplomat_core/src/hir/methods/borrowing_field.rs
@@ -0,0 +1,357 @@
+//! Tools for traversing all borrows in method parameters and return types, transitively
+//!
+//! This is useful for backends which wish to "splat out" lifetime edge codegen for methods,
+//! linking each borrowed input param/field (however deep it may be in a struct) to a borrowed output param/field.
+
+use std::fmt::{self, Write};
+
+use smallvec::SmallVec;
+
+use crate::hir::{
+ paths, Borrow, Ident, Method, SelfType, Slice, StructPath, TyPosition, Type, TypeContext,
+};
+
+use crate::hir::lifetimes::{Lifetime, Lifetimes, MaybeStatic};
+
+/// An id for indexing into a [`BorrowingFieldsVisitor`].
+#[derive(Copy, Clone, Debug)]
+struct ParentId(usize);
+
+impl ParentId {
+ /// Pushes a new parent to the vec, returning the corresponding [`ParentId`].
+ fn new<'m>(
+ parent: Option<ParentId>,
+ name: &'m Ident,
+ parents: &mut SmallVec<[(Option<ParentId>, &'m Ident); 4]>,
+ ) -> Self {
+ let this = ParentId(parents.len());
+ parents.push((parent, name));
+ this
+ }
+}
+
+/// Convenience const representing the number of nested structs a [`BorrowingFieldVisitor`]
+/// can hold inline before needing to dynamically allocate.
+const INLINE_NUM_PARENTS: usize = 4;
+
+/// Convenience const representing the number of borrowed fields a [`BorrowingFieldVisitor`]
+/// can hold inline before needing to dynamically allocate.
+const INLINE_NUM_LEAVES: usize = 8;
+
+/// A tree of lifetimes mapping onto a specific instantiation of a type tree.
+///
+/// Each `BorrowingFieldsVisitor` corresponds to the type of an input of a method.
+///
+/// Obtain from [`Method::borrowing_field_visitor()`].
+pub struct BorrowingFieldVisitor<'m> {
+ parents: SmallVec<[(Option<ParentId>, &'m Ident); INLINE_NUM_PARENTS]>,
+ leaves: SmallVec<[BorrowingFieldVisitorLeaf; INLINE_NUM_LEAVES]>,
+}
+
+/// A leaf of a lifetime tree capable of tracking its parents.
+#[derive(Copy, Clone)]
+pub struct BorrowingField<'m> {
+ /// All inner nodes in the tree. When tracing from the root, we jump around
+ /// this slice based on indices, but don't necessarily use all of them.
+ parents: &'m [(Option<ParentId>, &'m Ident)],
+
+ /// The unpacked field that is a leaf on the tree.
+ leaf: &'m BorrowingFieldVisitorLeaf,
+}
+
+/// Non-recursive input-output types that contain lifetimes
+enum BorrowingFieldVisitorLeaf {
+ Opaque(ParentId, MaybeStatic<Lifetime>, Lifetimes),
+ Slice(ParentId, MaybeStatic<Lifetime>),
+}
+
+impl<'m> BorrowingFieldVisitor<'m> {
+ /// Visits every borrowing field and method lifetime that it uses.
+ ///
+ /// The idea is that you could use this to construct a mapping from
+ /// `Lifetime`s to `BorrowingField`s. We choose to use a visitor
+ /// pattern to avoid having to
+ ///
+ /// This would be convenient in the JavaScript backend where if you're
+ /// returning an `NonOpaque<'a, 'b>` from Rust, you need to pass a
+ /// `[[all input borrowing fields with 'a], [all input borrowing fields with 'b]]`
+ /// array into `NonOpaque`'s constructor.
+ ///
+ /// Alternatively, you could use such a map in the C++ backend by recursing
+ /// down the return type and keeping track of which fields you've recursed
+ /// into so far, and when you hit some lifetime 'a, generate docs saying
+ /// "path.to.current.field must be outlived by {borrowing fields of input that
+ /// contain 'a}".
+ pub fn visit_borrowing_fields<'a, F>(&'a self, mut visit: F)
+ where
+ F: FnMut(MaybeStatic<Lifetime>, BorrowingField<'a>),
+ {
+ for leaf in self.leaves.iter() {
+ let borrowing_field = BorrowingField {
+ parents: self.parents.as_slice(),
+ leaf,
+ };
+
+ match leaf {
+ BorrowingFieldVisitorLeaf::Opaque(_, lt, method_lifetimes) => {
+ visit(*lt, borrowing_field);
+ for lt in method_lifetimes.lifetimes() {
+ visit(lt, borrowing_field);
+ }
+ }
+ BorrowingFieldVisitorLeaf::Slice(_, lt) => {
+ visit(*lt, borrowing_field);
+ }
+ }
+ }
+ }
+
+ /// Returns a new `BorrowingFieldsVisitor` containing all the lifetime trees of the arguments
+ /// in only two allocations.
+ pub(crate) fn new(method: &'m Method, tcx: &'m TypeContext, self_name: &'m Ident) -> Self {
+ let (parents, leaves) = method
+ .param_self
+ .as_ref()
+ .map(|param_self| param_self.field_leaf_lifetime_counts(tcx))
+ .into_iter()
+ .chain(
+ method
+ .params
+ .iter()
+ .map(|param| param.ty.field_leaf_lifetime_counts(tcx)),
+ )
+ .reduce(|acc, x| (acc.0 + x.0, acc.1 + x.1))
+ .map(|(num_fields, num_leaves)| {
+ let num_params = method.params.len() + usize::from(method.param_self.is_some());
+ let mut parents = SmallVec::with_capacity(num_fields + num_params);
+ let mut leaves = SmallVec::with_capacity(num_leaves);
+ let method_lifetimes = method.method_lifetimes();
+
+ if let Some(param_self) = method.param_self.as_ref() {
+ let parent = ParentId::new(None, self_name, &mut parents);
+ match ¶m_self.ty {
+ SelfType::Opaque(ty) => {
+ Self::visit_opaque(
+ &ty.lifetimes,
+ &ty.borrowed().lifetime,
+ parent,
+ &method_lifetimes,
+ &mut leaves,
+ );
+ }
+ SelfType::Struct(ty) => {
+ Self::visit_struct(
+ ty,
+ tcx,
+ parent,
+ &method_lifetimes,
+ &mut parents,
+ &mut leaves,
+ );
+ }
+ SelfType::Enum(_) => {}
+ }
+ }
+
+ for param in method.params.iter() {
+ let parent = ParentId::new(None, param.name.as_ref(), &mut parents);
+ Self::from_type(
+ ¶m.ty,
+ tcx,
+ parent,
+ &method_lifetimes,
+ &mut parents,
+ &mut leaves,
+ );
+ }
+
+ // sanity check that the preallocations were correct
+ debug_assert_eq!(
+ parents.capacity(),
+ std::cmp::max(INLINE_NUM_PARENTS, num_fields + num_params)
+ );
+ debug_assert_eq!(
+ leaves.capacity(),
+ std::cmp::max(INLINE_NUM_LEAVES, num_leaves)
+ );
+ (parents, leaves)
+ })
+ .unwrap_or_default();
+
+ Self { parents, leaves }
+ }
+
+ /// Returns a new [`BorrowingFieldsVisitor`] corresponding to a type.
+ fn from_type<P: TyPosition<StructPath = StructPath, OpaqueOwnership = Borrow>>(
+ ty: &'m Type<P>,
+ tcx: &'m TypeContext,
+ parent: ParentId,
+ method_lifetimes: &Lifetimes,
+ parents: &mut SmallVec<[(Option<ParentId>, &'m Ident); 4]>,
+ leaves: &mut SmallVec<[BorrowingFieldVisitorLeaf; 8]>,
+ ) {
+ match ty {
+ Type::Opaque(path) => {
+ Self::visit_opaque(
+ &path.lifetimes,
+ &path.borrowed().lifetime,
+ parent,
+ method_lifetimes,
+ leaves,
+ );
+ }
+ Type::Slice(slice) => {
+ Self::visit_slice(slice, parent, method_lifetimes, leaves);
+ }
+ Type::Struct(path) => {
+ Self::visit_struct(path, tcx, parent, method_lifetimes, parents, leaves);
+ }
+ _ => {}
+ }
+ }
+
+ /// Add an opaque as a leaf during construction of a [`BorrowingFieldsVisitor`].
+ fn visit_opaque(
+ lifetimes: &'m Lifetimes,
+ borrow: &'m MaybeStatic<Lifetime>,
+ parent: ParentId,
+ method_lifetimes: &Lifetimes,
+ leaves: &mut SmallVec<[BorrowingFieldVisitorLeaf; 8]>,
+ ) {
+ let method_borrow_lifetime =
+ borrow.flat_map_nonstatic(|lt| lt.as_method_lifetime(method_lifetimes));
+ let method_type_lifetimes = lifetimes.as_method_lifetimes(method_lifetimes);
+ leaves.push(BorrowingFieldVisitorLeaf::Opaque(
+ parent,
+ method_borrow_lifetime,
+ method_type_lifetimes,
+ ));
+ }
+
+ /// Add a slice as a leaf during construction of a [`BorrowingFieldsVisitor`].
+ fn visit_slice(
+ slice: &Slice,
+ parent: ParentId,
+ method_lifetimes: &Lifetimes,
+ leaves: &mut SmallVec<[BorrowingFieldVisitorLeaf; 8]>,
+ ) {
+ if let Some(lifetime) = slice.lifetime() {
+ let method_lifetime =
+ lifetime.flat_map_nonstatic(|lt| lt.as_method_lifetime(method_lifetimes));
+ leaves.push(BorrowingFieldVisitorLeaf::Slice(parent, method_lifetime));
+ }
+ }
+
+ /// Add a struct as a parent and recurse down leaves during construction of a
+ /// [`BorrowingFieldsVisitor`].
+ fn visit_struct(
+ ty: &paths::StructPath,
+ tcx: &'m TypeContext,
+ parent: ParentId,
+ method_lifetimes: &Lifetimes,
+ parents: &mut SmallVec<[(Option<ParentId>, &'m Ident); 4]>,
+ leaves: &mut SmallVec<[BorrowingFieldVisitorLeaf; 8]>,
+ ) {
+ let method_type_lifetimes = ty.lifetimes.as_method_lifetimes(method_lifetimes);
+ for field in ty.resolve(tcx).fields.iter() {
+ Self::from_type(
+ &field.ty,
+ tcx,
+ ParentId::new(Some(parent), field.name.as_ref(), parents),
+ &method_type_lifetimes,
+ parents,
+ leaves,
+ );
+ }
+ }
+}
+
+impl<'m> BorrowingField<'m> {
+ /// Visit fields in order.
+ ///
+ /// If `self` represents the field `param.first.second`, then calling [`BorrowingField::trace`]
+ /// will visit the following in order: `"param"`, `"first"`, `"second"`.
+ pub fn backtrace<F>(&self, mut visit: F)
+ where
+ F: FnMut(usize, &'m Ident),
+ {
+ let (parent, ident) = match self.leaf {
+ BorrowingFieldVisitorLeaf::Opaque(id, ..) | BorrowingFieldVisitorLeaf::Slice(id, _) => {
+ self.parents[id.0]
+ }
+ };
+
+ self.backtrace_rec(parent, ident, &mut visit);
+ }
+
+ /// Recursively visits fields in order from root to leaf by building up the
+ /// stack, and then visiting fields as it unwinds.
+ fn backtrace_rec<F>(&self, parent: Option<ParentId>, ident: &'m Ident, visit: &mut F) -> usize
+ where
+ F: FnMut(usize, &'m Ident),
+ {
+ let from_end = if let Some(id) = parent {
+ let (parent, ident) = self.parents[id.0];
+ self.backtrace_rec(parent, ident, visit)
+ } else {
+ 0
+ };
+
+ visit(from_end, ident);
+
+ from_end + 1
+ }
+
+ /// Fallibly visits fields in order.
+ ///
+ /// This method is similar to [`BorrowinfField::backtrace`], but short-circuits
+ /// when an `Err` is returned.
+ pub fn try_backtrace<F, E>(&self, mut visit: F) -> Result<(), E>
+ where
+ F: FnMut(usize, &'m Ident) -> Result<(), E>,
+ {
+ let (parent, ident) = match self.leaf {
+ BorrowingFieldVisitorLeaf::Opaque(id, ..) | BorrowingFieldVisitorLeaf::Slice(id, _) => {
+ self.parents[id.0]
+ }
+ };
+
+ self.try_backtrace_rec(parent, ident, &mut visit)?;
+
+ Ok(())
+ }
+
+ /// Recursively visits fields in order from root to leaf by building up the
+ /// stack, and then visiting fields as it unwinds.
+ fn try_backtrace_rec<F, E>(
+ &self,
+ parent: Option<ParentId>,
+ ident: &'m Ident,
+ visit: &mut F,
+ ) -> Result<usize, E>
+ where
+ F: FnMut(usize, &'m Ident) -> Result<(), E>,
+ {
+ let from_end = if let Some(id) = parent {
+ let (parent, ident) = self.parents[id.0];
+ self.try_backtrace_rec(parent, ident, visit)?
+ } else {
+ 0
+ };
+
+ visit(from_end, ident)?;
+
+ Ok(from_end + 1)
+ }
+}
+
+impl<'m> fmt::Display for BorrowingField<'m> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.try_backtrace(|i, ident| {
+ if i != 0 {
+ f.write_char('.')?;
+ }
+ f.write_str(ident.as_str())
+ })
+ }
+}
diff --git a/crates/diplomat_core/src/hir/methods/borrowing_param.rs b/crates/diplomat_core/src/hir/methods/borrowing_param.rs
new file mode 100644
index 0000000..4069d70
--- /dev/null
+++ b/crates/diplomat_core/src/hir/methods/borrowing_param.rs
@@ -0,0 +1,354 @@
+//! Tools for traversing all borrows in method parameters and return types, shallowly
+//!
+//! This is useful for backends which wish to figure out the borrowing relationships between parameters
+//! and return values,
+//! and then delegate how lifetimes get mapped to fields to the codegen for those types respectively.
+//!
+//!
+//! # Typical usage
+//!
+//! In managed languages, to ensure the GC doesn't prematurely clean up the borrowed-from object,
+//! we use the technique of stashing a borrowed-from object as a field of a borrowed-to object.
+//! This is called a "lifetime edge" (since it's an edge in the GC graph). An "edge array" is typically
+//! a stash of all the edges on an object, or all the edges corresponding to a particular lifetime.
+//!
+//! This stashing is primarily driven by method codegen, which is where actual borrow relationships can be set up.
+//!
+//! Typically, codegen for a method should instantiate a [`BorrowedParamVisitor`] and uses it to [`BorrowedParamVisitor::visit_param()`] each input parameter to determine if it needs to be linked into an edge.
+//! Whilst doing so, it may return useful [`ParamBorrowInfo`] which provides additional information on handling structs and slices. More on structs later.
+//! At the end of the visiting, [`BorrowedLifetimeInfo::borrow_map()`] can be called to get a list of relevant input edges: each lifetime that participates in borrowing,
+//! and a list of parameter names that it borrows from.
+//!
+//! This visitor will automatically handle transitive lifetime relationships as well.
+//!
+//! These edges should be put together into "edge arrays" which then get passed down to output types, handling transitive lifetimes as necessary.
+//!
+//! ## Method codegen for slices
+//!
+//! Slices are strange: in managed languages a host slice will not be a Rust slice, unlike other borrowed host values (a borrowed host opaque is just a borrowed Rust
+//! opaque). We need to convert these across the boundary by allocating a temporary arena or something.
+//!
+//! If the slice doesn't participate in borrowing, this is easy: just allocate a temporary arena. However, if not, we need to allocate an arena with a finalizer (or something similar)
+//! and add that arena to the edge instead. [`LifetimeEdgeKind`] has special handling for this.
+//!
+//! Whether or not the slice participates in borrowing is found out from the [`ParamBorrowInfo`] returned by [`BorrowedParamVisitor::visit_param()`].
+//!
+//! # Method codegen for struct params
+//!
+//! Structs can contain many lifetimes and have relationships between them. Generally, a Diplomat struct is a "bag of stuff"; it is converted to a host value that is structlike, with fields
+//! individually converted.
+//!
+//! Diplomat enforces that struct lifetime bounds never imply additional bounds on methods during HIR validation, so the relationships between struct
+//! lifetimes are not relevant for code here (and as such you should hopefully never need to call [`LifetimeEnv::all_longer_lifetimes()`])
+//! for a struct env).
+//!
+//! The borrowing technique in this module allows a lot of things to be delegated to structs. As will be explained below, structs will have:
+//!
+//! - A `fields_for_lifetime_foo()` set of methods that returns all non-slice fields corresponding to a lifetime
+//! - "append array" outparams for stashing edges when converting from host-to-Rust
+//! - Some way of passing down edge arrays to individual borrowed fields when constructing Rust-to-host
+//!
+//! In methods, when constructing the edge arrays, `fields_for_lifetime_foo()` for every borrowed param can be appended to each
+//! one whenever [`LifetimeEdgeKind::StructLifetime`] asks you to do so. The code needs to handle lifetime
+//! transitivity, since the struct will not be doing so. Fortunately the edges produced by [`BorrowedParamVisitor`] already do so.
+//!
+//! Then, when converting structs host-to-Rust, every edge array relevant to a struct lifetime should be passed in for an append array. Append arrays
+//! are nested arrays: they are an array of edge arrays. The struct will append further things to the edge array.
+//!
+//! Finally, when converting Rust-to-host, if producing a struct, make sure to pass in all the edge arrays.
+//!
+//! # Struct codegen
+//!
+//! At a high level, struct codegen needs to deal with lifetimes in three places:
+//!
+//! - A `fields_for_lifetime_foo()` set of methods that returns all non-slice fields corresponding to a lifetime
+//! - "append array" outparams for stashing edges when converting from host-to-Rust
+//! - Some way of passing down edge arrays to individual borrowed fields when constructing Rust-to-host
+//!
+//! The backend may choose to handle just slices via append arrays, or handle all borrowing there, in which case `fields_for_lifetime_foo()` isn't necessary.
+//!
+//! `fields_for_lifetime_foo()` should return an iterator/list/etc corresponding to each field that *directly* borrows from lifetime foo.
+//! This may include calling `fields_for_lifetime_bar()` on fields that are themselves structs. As mentioned previously, lifetime relationships
+//! are handled by methods and don't need to be dealt with here. There are no helpers for doing this but it's just a matter of
+//! filtering for fields using the lifetime, except for handling nested structs ([`StructBorrowInfo::compute_for_struct_field()`])
+//!
+//! Append arrays are similarly straightforward: for each lifetime on the struct, the host-to-Rust constructor should accept a list of edge arrays. For slice fields,
+//! if the appropriate list is nonempty, the slice's arena edge should be appended to all of the edge arrays in it. These arrays should also be passed down to struct fields
+//! appropriately: [`StructBorrowInfo::compute_for_struct_field()`] is super helpful for getting a list edges something should get passed down to.
+//!
+//! Finally, when converting Rust-to-host, the relevant lifetime edges should be proxied down to the final host type constructors who can stash them wherever needed.
+//! This is one again a matter of filtering fields by the lifetimes they use, and for nested structs you can use [`StructBorrowInfo::compute_for_struct_field()`].
+
+use std::collections::{BTreeMap, BTreeSet};
+
+use crate::hir::{self, Method, StructDef, StructPath, TyPosition, TypeContext};
+
+use crate::hir::lifetimes::{Lifetime, LifetimeEnv, MaybeStatic};
+use crate::hir::ty_position::StructPathLike;
+
+/// A visitor for processing method parameters/returns and understanding their borrowing relationships, shallowly.
+///
+/// This produces a list of lifetime "edges" per lifetime in the output producing a borrow.
+///
+/// Each `BorrowingFieldsVisitor` corresponds to the type of an input of a method.
+///
+/// Obtain from [`Method::borrowing_param_visitor()`].
+pub struct BorrowingParamVisitor<'tcx> {
+ tcx: &'tcx TypeContext,
+ used_method_lifetimes: BTreeSet<Lifetime>,
+ borrow_map: BTreeMap<Lifetime, BorrowedLifetimeInfo<'tcx>>,
+}
+
+/// A single lifetime "edge" from a parameter to a value
+#[non_exhaustive]
+#[derive(Clone, Debug)]
+pub struct LifetimeEdge<'tcx> {
+ pub param_name: String,
+ pub kind: LifetimeEdgeKind<'tcx>,
+}
+
+#[non_exhaustive]
+#[derive(Copy, Clone, Debug)]
+pub enum LifetimeEdgeKind<'tcx> {
+ /// Just an opaque parameter directly being borrowed.
+ OpaqueParam,
+ /// A slice being converted and then borrowed. These often need to be handled differently
+ /// when they are borrowed as the borrow will need to create an edge
+ SliceParam,
+ /// A lifetime parameter of a struct, given the lifetime context and the struct-def lifetime for that struct.
+ ///
+ /// The boolean is whether or not the struct is optional.
+ ///
+ /// Using this, you can generate code that "asks" the struct for the lifetime-relevant field edges
+ StructLifetime(&'tcx LifetimeEnv, Lifetime, bool),
+}
+
+#[non_exhaustive]
+#[derive(Clone, Debug)]
+pub struct BorrowedLifetimeInfo<'tcx> {
+ // Initializers for all inputs to the edge array from parameters, except for slices (slices get handled
+ // differently)
+ pub incoming_edges: Vec<LifetimeEdge<'tcx>>,
+ // All lifetimes longer than this. When this lifetime is borrowed from, data corresponding to
+ // the other lifetimes may also be borrowed from.
+ pub all_longer_lifetimes: BTreeSet<Lifetime>,
+}
+
+impl<'tcx> BorrowingParamVisitor<'tcx> {
+ pub(crate) fn new(method: &'tcx Method, tcx: &'tcx TypeContext) -> Self {
+ let used_method_lifetimes = method.output.used_method_lifetimes();
+ let borrow_map = used_method_lifetimes
+ .iter()
+ .map(|lt| {
+ (
+ *lt,
+ BorrowedLifetimeInfo {
+ incoming_edges: Vec::new(),
+ all_longer_lifetimes: method
+ .lifetime_env
+ .all_longer_lifetimes(lt)
+ .collect(),
+ },
+ )
+ })
+ .collect();
+ BorrowingParamVisitor {
+ tcx,
+ used_method_lifetimes,
+ borrow_map,
+ }
+ }
+
+ /// Get the cached list of used method lifetimes. Same as calling `.used_method_lifetimes()` on `method.output`
+ pub fn used_method_lifetimes(&self) -> &BTreeSet<Lifetime> {
+ &self.used_method_lifetimes
+ }
+
+ /// Get the final borrow map, listing lifetime edges for each output lfietime
+ pub fn borrow_map(self) -> BTreeMap<Lifetime, BorrowedLifetimeInfo<'tcx>> {
+ self.borrow_map
+ }
+
+ /// Processes a parameter, adding it to the borrow_map for any lifetimes it references. Returns further information about the type of borrow.
+ ///
+ /// This basically boils down to: For each lifetime that is actually relevant to borrowing in this method, check if that
+ /// lifetime or lifetimes longer than it are used by this parameter. In other words, check if
+ /// it is possible for data in the return type with this lifetime to have been borrowed from this parameter.
+ /// If so, add code that will yield the ownership-relevant parts of this object to incoming_edges for that lifetime.
+ pub fn visit_param<P: TyPosition<StructPath = StructPath>>(
+ &mut self,
+ ty: &hir::Type<P>,
+ param_name: &str,
+ ) -> ParamBorrowInfo<'tcx> {
+ let mut is_borrowed = false;
+ if self.used_method_lifetimes.is_empty() {
+ if let hir::Type::Slice(..) = *ty {
+ return ParamBorrowInfo::TemporarySlice;
+ } else {
+ return ParamBorrowInfo::NotBorrowed;
+ }
+ }
+
+ // Structs have special handling: structs are purely Dart-side, so if you borrow
+ // from a struct, you really are borrowing from the internal fields.
+ if let hir::Type::Struct(s) = ty {
+ let mut borrowed_struct_lifetime_map = BTreeMap::<Lifetime, BTreeSet<Lifetime>>::new();
+ let link = s.link_lifetimes(self.tcx);
+ for (method_lifetime, method_lifetime_info) in &mut self.borrow_map {
+ // Note that ty.lifetimes()/s.lifetimes() is lifetimes
+ // in the *use* context, i.e. lifetimes on the Type that reference the
+ // indices of the method's lifetime arrays. Their *order* references
+ // the indices of the underlying struct def. We need to link the two,
+ // since the _fields_for_lifetime_foo() methods are named after
+ // the *def* context lifetime.
+ //
+ // Concretely, if we have struct `Foo<'a, 'b>` and our method
+ // accepts `Foo<'x, 'y>`, we need to output _fields_for_lifetime_a()/b not x/y.
+ //
+ // This is a struct so lifetimes_def_only() is fine to call
+ for (use_lt, def_lt) in link.lifetimes_def_only() {
+ if let MaybeStatic::NonStatic(use_lt) = use_lt {
+ if method_lifetime_info.all_longer_lifetimes.contains(&use_lt) {
+ let edge = LifetimeEdge {
+ param_name: param_name.into(),
+ kind: LifetimeEdgeKind::StructLifetime(
+ link.def_env(),
+ def_lt,
+ ty.is_option(),
+ ),
+ };
+ method_lifetime_info.incoming_edges.push(edge);
+
+ is_borrowed = true;
+
+ borrowed_struct_lifetime_map
+ .entry(def_lt)
+ .or_default()
+ .insert(*method_lifetime);
+ // Do *not* break the inner loop here: even if we found *one* matching lifetime
+ // in this struct that may not be all of them, there may be some other fields that are borrowed
+ }
+ }
+ }
+ }
+ if is_borrowed {
+ ParamBorrowInfo::Struct(StructBorrowInfo {
+ env: link.def_env(),
+ borrowed_struct_lifetime_map,
+ })
+ } else {
+ ParamBorrowInfo::NotBorrowed
+ }
+ } else {
+ for method_lifetime in self.borrow_map.values_mut() {
+ for lt in ty.lifetimes() {
+ if let MaybeStatic::NonStatic(lt) = lt {
+ if method_lifetime.all_longer_lifetimes.contains(<) {
+ let kind = match ty {
+ hir::Type::Slice(..) => LifetimeEdgeKind::SliceParam,
+ hir::Type::Opaque(..) => LifetimeEdgeKind::OpaqueParam,
+ _ => unreachable!("Types other than slices, opaques, and structs cannot have lifetimes")
+ };
+
+ let edge = LifetimeEdge {
+ param_name: param_name.into(),
+ kind,
+ };
+
+ method_lifetime.incoming_edges.push(edge);
+ is_borrowed = true;
+ // Break the inner loop: we've already determined this
+ break;
+ }
+ }
+ }
+ }
+ match (is_borrowed, ty) {
+ (true, &hir::Type::Slice(..)) => ParamBorrowInfo::BorrowedSlice,
+ (false, &hir::Type::Slice(..)) => ParamBorrowInfo::TemporarySlice,
+ (false, _) => ParamBorrowInfo::NotBorrowed,
+ (true, _) => ParamBorrowInfo::BorrowedOpaque,
+ }
+ }
+ }
+}
+
+/// Information relevant to borrowing for producing conversions
+#[derive(Clone, Debug)]
+#[non_exhaustive]
+pub enum ParamBorrowInfo<'tcx> {
+ /// No borrowing constraints. This means the parameter
+ /// is not borrowed by the output and also does not need temporary borrows
+ NotBorrowed,
+ /// A slice that is not borrowed by the output (but will still need temporary allocation)
+ TemporarySlice,
+ /// A slice that is borrowed by the output
+ BorrowedSlice,
+ /// A struct parameter that is borrowed by the output
+ Struct(StructBorrowInfo<'tcx>),
+ /// An opaque type that is borrowed
+ BorrowedOpaque,
+}
+
+/// Information about the lifetimes of a struct parameter that are borrowed by a method output or by a wrapping struct
+#[derive(Clone, Debug)]
+#[non_exhaustive]
+pub struct StructBorrowInfo<'tcx> {
+ /// This is the struct's lifetime environment
+ pub env: &'tcx LifetimeEnv,
+ /// A map from (borrow-relevant) struct lifetimes to lifetimes in the method (or wrapping struct) that may flow from it
+ pub borrowed_struct_lifetime_map: BTreeMap<Lifetime, BTreeSet<Lifetime>>,
+}
+
+impl<'tcx> StructBorrowInfo<'tcx> {
+ /// Get borrowing info for a struct field, if it does indeed borrow
+ ///
+ /// The lifetime map produced here does not handle lifetime dependencies: the expectation is that the struct
+ /// machinery generated by this will be called by method code that handles these dependencies. We try to handle
+ /// lifetime dependencies in ONE place.
+ pub fn compute_for_struct_field<P: TyPosition>(
+ struc: &StructDef<P>,
+ field: &P::StructPath,
+ tcx: &'tcx TypeContext,
+ ) -> Option<Self> {
+ if field.lifetimes().as_slice().is_empty() {
+ return None;
+ }
+
+ let mut borrowed_struct_lifetime_map = BTreeMap::<Lifetime, BTreeSet<Lifetime>>::new();
+
+ let link = field.link_lifetimes(tcx);
+
+ for outer_lt in struc.lifetimes.all_lifetimes() {
+ // Note that field.lifetimes()
+ // in the *use* context, i.e. lifetimes on the Type that reference the
+ // indices of the outer struct's lifetime arrays. Their *order* references
+ // the indices of the underlying struct def. We need to link the two,
+ // since the _fields_for_lifetime_foo() methods are named after
+ // the *def* context lifetime.
+ //
+ // This is a struct so lifetimes_def_only() is fine to call
+ for (use_lt, def_lt) in link.lifetimes_def_only() {
+ if let MaybeStatic::NonStatic(use_lt) = use_lt {
+ // We do *not* need to transitively check for longer lifetimes here:
+ //
+ if outer_lt == use_lt {
+ borrowed_struct_lifetime_map
+ .entry(def_lt)
+ .or_default()
+ .insert(outer_lt);
+ }
+ }
+ }
+ }
+ if borrowed_struct_lifetime_map.is_empty() {
+ // if the inner struct is only statics
+ None
+ } else {
+ Some(StructBorrowInfo {
+ env: link.def_env(),
+ borrowed_struct_lifetime_map,
+ })
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/hir/mod.rs b/crates/diplomat_core/src/hir/mod.rs
new file mode 100644
index 0000000..4a0959d
--- /dev/null
+++ b/crates/diplomat_core/src/hir/mod.rs
@@ -0,0 +1,31 @@
+//! Experimental high-level representation (HIR) for Diplomat.
+//!
+//! Enabled with the `"hir"` Cargo feature
+
+mod attrs;
+mod defs;
+mod elision;
+mod lifetimes;
+mod lowering;
+mod methods;
+mod paths;
+mod primitives;
+mod ty_position;
+mod type_context;
+mod types;
+pub use attrs::*;
+pub use defs::*;
+pub(super) use elision::*;
+pub use lifetimes::*;
+pub(super) use lowering::*;
+pub use methods::*;
+pub use paths::*;
+pub use primitives::*;
+pub use ty_position::*;
+pub use type_context::*;
+pub use types::*;
+
+pub use lowering::{ErrorAndContext, ErrorContext, LoweringError};
+
+pub use crate::ast::{Docs, DocsUrlGenerator};
+pub use strck::ident::rust::{Ident, IdentBuf};
diff --git a/crates/diplomat_core/src/hir/paths.rs b/crates/diplomat_core/src/hir/paths.rs
new file mode 100644
index 0000000..6c2125e
--- /dev/null
+++ b/crates/diplomat_core/src/hir/paths.rs
@@ -0,0 +1,220 @@
+use super::lifetimes::{Lifetimes, LinkedLifetimes};
+use super::{
+ Borrow, EnumDef, EnumId, Everywhere, OpaqueDef, OpaqueId, OpaqueOwner, OutStructDef,
+ OutputOnly, ReturnableStructDef, StructDef, TraitId, TyPosition, TypeContext,
+};
+
+/// Path to a struct that may appear as an output.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum ReturnableStructPath {
+ Struct(StructPath),
+ OutStruct(OutStructPath),
+}
+
+/// Path to a struct that can only be used as an output.
+pub type OutStructPath = StructPath<OutputOnly>;
+
+/// Path to a struct that can be used in inputs and outputs.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct StructPath<P: TyPosition = Everywhere> {
+ pub lifetimes: Lifetimes,
+ pub tcx_id: P::StructId,
+}
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct TraitPath {
+ pub lifetimes: Lifetimes,
+ pub tcx_id: TraitId,
+}
+
+/// Non-instantiable enum to denote the trait path in
+/// TyPositions that don't allow traits (anything not InputOnly)
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum NoTraitPath {}
+
+/// Path to an opaque.
+///
+/// There are three kinds of opaques that Diplomat uses, so this type has two
+/// generic arguments to differentiate between the three, while still showing
+/// that the three are all paths to opaques. The monomorphized versions that
+/// Diplomat uses are:
+///
+/// 1. `OpaquePath<Optional, MaybeOwn>`: Opaques in return types,
+/// which can be optional and either owned or borrowed.
+/// 2. `OpaquePath<Optional, Borrow>`: Opaques in method parameters, which can
+/// be optional but must be borrowed, since most languages don't have a way to
+/// entirely give up ownership of a value.
+/// 3. `OpaquePath<NonOptional, Borrow>`: Opaques in the `&self` position, which
+/// cannot be optional and must be borrowed for the same reason as above.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct OpaquePath<Opt, Owner> {
+ pub lifetimes: Lifetimes,
+ pub optional: Opt,
+ pub owner: Owner,
+ pub tcx_id: OpaqueId,
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct Optional(pub(super) bool);
+
+#[derive(Debug, Copy, Clone)]
+#[allow(clippy::exhaustive_structs)] // marker type
+pub struct NonOptional;
+
+impl<Owner: OpaqueOwner> OpaquePath<Optional, Owner> {
+ pub fn is_optional(&self) -> bool {
+ self.optional.0
+ }
+}
+
+impl<Owner: OpaqueOwner> OpaquePath<NonOptional, Owner> {
+ pub fn wrap_optional(self) -> OpaquePath<Optional, Owner> {
+ OpaquePath {
+ lifetimes: self.lifetimes,
+ optional: Optional(false),
+ owner: self.owner,
+ tcx_id: self.tcx_id,
+ }
+ }
+}
+
+impl<Opt> OpaquePath<Opt, MaybeOwn> {
+ pub fn as_borrowed(&self) -> Option<&Borrow> {
+ self.owner.as_borrowed()
+ }
+}
+
+impl<Opt> OpaquePath<Opt, Borrow> {
+ pub fn borrowed(&self) -> &Borrow {
+ &self.owner
+ }
+}
+
+/// Path to an enum.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct EnumPath {
+ pub tcx_id: EnumId,
+}
+
+/// Determine whether a pointer to an opaque type is owned or borrowed.
+///
+/// Since owned opaques cannot be used as inputs, this only appears in output types.
+#[derive(Copy, Clone, Debug)]
+#[allow(clippy::exhaustive_enums)] // only two answers to this question
+pub enum MaybeOwn {
+ Own,
+ Borrow(Borrow),
+}
+
+impl MaybeOwn {
+ pub fn as_borrowed(&self) -> Option<&Borrow> {
+ match self {
+ MaybeOwn::Own => None,
+ MaybeOwn::Borrow(borrow) => Some(borrow),
+ }
+ }
+}
+
+impl ReturnableStructPath {
+ pub fn resolve<'tcx>(&self, tcx: &'tcx TypeContext) -> ReturnableStructDef<'tcx> {
+ match self {
+ ReturnableStructPath::Struct(path) => ReturnableStructDef::Struct(path.resolve(tcx)),
+ ReturnableStructPath::OutStruct(path) => {
+ ReturnableStructDef::OutStruct(path.resolve(tcx))
+ }
+ }
+ }
+
+ pub(crate) fn lifetimes(&self) -> &Lifetimes {
+ match self {
+ Self::Struct(p) => &p.lifetimes,
+ Self::OutStruct(p) => &p.lifetimes,
+ }
+ }
+}
+
+impl<P: TyPosition> StructPath<P> {
+ /// Returns a new [`EnumPath`].
+ pub(super) fn new(lifetimes: Lifetimes, tcx_id: P::StructId) -> Self {
+ Self { lifetimes, tcx_id }
+ }
+}
+impl StructPath {
+ /// Returns the [`StructDef`] that this path references.
+ pub fn resolve<'tcx>(&self, tcx: &'tcx TypeContext) -> &'tcx StructDef {
+ tcx.resolve_struct(self.tcx_id)
+ }
+}
+
+impl OutStructPath {
+ /// Returns the [`OutStructDef`] that this path references.
+ pub fn resolve<'tcx>(&self, tcx: &'tcx TypeContext) -> &'tcx OutStructDef {
+ tcx.resolve_out_struct(self.tcx_id)
+ }
+
+ /// Get a map of lifetimes used on this path to lifetimes as named in the def site. See [`LinkedLifetimes`]
+ /// for more information.
+ pub fn link_lifetimes<'def, 'tcx>(
+ &'def self,
+ tcx: &'tcx TypeContext,
+ ) -> LinkedLifetimes<'def, 'tcx> {
+ let struc = self.resolve(tcx);
+ let env = &struc.lifetimes;
+ LinkedLifetimes::new(env, None, &self.lifetimes)
+ }
+}
+
+impl<Opt, Owner> OpaquePath<Opt, Owner> {
+ /// Returns a new [`OpaquePath`].
+ pub(super) fn new(lifetimes: Lifetimes, optional: Opt, owner: Owner, tcx_id: OpaqueId) -> Self {
+ Self {
+ lifetimes,
+ optional,
+ owner,
+ tcx_id,
+ }
+ }
+
+ /// Returns the [`OpaqueDef`] that this path references.
+ pub fn resolve<'tcx>(&self, tcx: &'tcx TypeContext) -> &'tcx OpaqueDef {
+ tcx.resolve_opaque(self.tcx_id)
+ }
+}
+
+impl<Opt, Owner: OpaqueOwner> OpaquePath<Opt, Owner> {
+ /// Get a map of lifetimes used on this path to lifetimes as named in the def site. See [`LinkedLifetimes`]
+ /// for more information.
+ pub fn link_lifetimes<'def, 'tcx>(
+ &'def self,
+ tcx: &'tcx TypeContext,
+ ) -> LinkedLifetimes<'def, 'tcx> {
+ let opaque = self.resolve(tcx);
+ let env = &opaque.lifetimes;
+ LinkedLifetimes::new(env, self.owner.lifetime(), &self.lifetimes)
+ }
+}
+
+impl EnumPath {
+ /// Returns a new [`EnumPath`].
+ pub(super) fn new(tcx_id: EnumId) -> Self {
+ Self { tcx_id }
+ }
+
+ /// Returns the [`EnumDef`] that this path references.
+ pub fn resolve<'tcx>(&self, tcx: &'tcx TypeContext) -> &'tcx EnumDef {
+ tcx.resolve_enum(self.tcx_id)
+ }
+}
+
+impl TraitPath {
+ /// Returns a new [`TraitPath`].
+ pub(super) fn new(lifetimes: Lifetimes, tcx_id: TraitId) -> Self {
+ Self { lifetimes, tcx_id }
+ }
+}
diff --git a/crates/diplomat_core/src/hir/primitives.rs b/crates/diplomat_core/src/hir/primitives.rs
new file mode 100644
index 0000000..0966409
--- /dev/null
+++ b/crates/diplomat_core/src/hir/primitives.rs
@@ -0,0 +1,138 @@
+//! Primitives types that can cross the FFI boundary.
+use crate::ast;
+
+/// 8, 16, 32, and 64-bit signed and unsigned integers.
+#[derive(Copy, Clone, Debug)]
+#[allow(clippy::exhaustive_enums)] // there are only these
+pub enum IntType {
+ I8,
+ I16,
+ I32,
+ I64,
+ U8,
+ U16,
+ U32,
+ U64,
+}
+
+/// Platform-dependent signed and unsigned size types.
+#[derive(Copy, Clone, Debug)]
+#[allow(clippy::exhaustive_enums)] // there are only these
+pub enum IntSizeType {
+ Isize,
+ Usize,
+}
+
+/// 128-bit signed and unsigned integers.
+#[derive(Copy, Clone, Debug)]
+#[allow(clippy::exhaustive_enums)] // there are only these
+pub enum Int128Type {
+ I128,
+ U128,
+}
+
+/// 32 and 64-bit floating point numbers.
+#[derive(Copy, Clone, Debug)]
+#[allow(clippy::exhaustive_enums)] // there are only these
+pub enum FloatType {
+ F32,
+ F64,
+}
+
+/// All primitive types.
+#[derive(Copy, Clone, Debug)]
+#[allow(clippy::exhaustive_enums)] // there are only these
+pub enum PrimitiveType {
+ Bool,
+ Char,
+ /// a primitive byte that is not meant to be interpreted numerically
+ /// in languages that don't have fine-grained integer types
+ Byte,
+ Int(IntType),
+ IntSize(IntSizeType),
+ Int128(Int128Type),
+ Float(FloatType),
+}
+
+impl IntType {
+ /// Returns the string representation of `self`.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ IntType::I8 => "i8",
+ IntType::I16 => "i16",
+ IntType::I32 => "i32",
+ IntType::I64 => "i64",
+ IntType::U8 => "u8",
+ IntType::U16 => "u16",
+ IntType::U32 => "u32",
+ IntType::U64 => "u64",
+ }
+ }
+}
+
+impl IntSizeType {
+ /// Returns the string representation of `self`.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ IntSizeType::Isize => "isize",
+ IntSizeType::Usize => "usize",
+ }
+ }
+}
+
+impl Int128Type {
+ /// Returns the string representation of `self`.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Int128Type::I128 => "i128",
+ Int128Type::U128 => "u128",
+ }
+ }
+}
+
+impl FloatType {
+ /// Returns the string representation of `self`.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ FloatType::F32 => "f32",
+ FloatType::F64 => "f64",
+ }
+ }
+}
+
+impl PrimitiveType {
+ pub(super) fn from_ast(prim: ast::PrimitiveType) -> Self {
+ match prim {
+ ast::PrimitiveType::i8 => PrimitiveType::Int(IntType::I8),
+ ast::PrimitiveType::u8 => PrimitiveType::Int(IntType::U8),
+ ast::PrimitiveType::i16 => PrimitiveType::Int(IntType::I16),
+ ast::PrimitiveType::u16 => PrimitiveType::Int(IntType::U16),
+ ast::PrimitiveType::i32 => PrimitiveType::Int(IntType::I32),
+ ast::PrimitiveType::u32 => PrimitiveType::Int(IntType::U32),
+ ast::PrimitiveType::i64 => PrimitiveType::Int(IntType::I64),
+ ast::PrimitiveType::u64 => PrimitiveType::Int(IntType::U64),
+ ast::PrimitiveType::isize => PrimitiveType::IntSize(IntSizeType::Isize),
+ ast::PrimitiveType::usize => PrimitiveType::IntSize(IntSizeType::Usize),
+ ast::PrimitiveType::i128 => PrimitiveType::Int128(Int128Type::I128),
+ ast::PrimitiveType::u128 => PrimitiveType::Int128(Int128Type::U128),
+ ast::PrimitiveType::f32 => PrimitiveType::Float(FloatType::F32),
+ ast::PrimitiveType::f64 => PrimitiveType::Float(FloatType::F64),
+ ast::PrimitiveType::bool => PrimitiveType::Bool,
+ ast::PrimitiveType::char => PrimitiveType::Char,
+ ast::PrimitiveType::byte => PrimitiveType::Byte,
+ }
+ }
+
+ /// Returns the string representation of `self`.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ PrimitiveType::Bool => "bool",
+ PrimitiveType::Char => "char",
+ PrimitiveType::Byte => "byte",
+ PrimitiveType::Int(ty) => ty.as_str(),
+ PrimitiveType::IntSize(ty) => ty.as_str(),
+ PrimitiveType::Int128(ty) => ty.as_str(),
+ PrimitiveType::Float(ty) => ty.as_str(),
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__auto.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__auto.snap
new file mode 100644
index 0000000..16f185d
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__auto.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/hir/attrs.rs
+expression: output
+---
+Lowering error in Opaque::next: `iterator` not supported in backend tests
+Lowering error in Opaque::auto_doesnt_work_on_renames: Diplomat attribute rename gated on 'auto' but is not one that works with 'auto'
+Lowering error in Opaque::auto_doesnt_work_on_disables: Diplomat attribute disable gated on 'auto' but is not one that works with 'auto'
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__comparator.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__comparator.snap
new file mode 100644
index 0000000..a19891d
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__comparator.snap
@@ -0,0 +1,19 @@
+---
+source: core/src/hir/attrs.rs
+expression: output
+---
+Lowering error in Struct::comparison_other: Comparator's parameter must be identical to self
+Lowering error in Struct::comparison_correct: Cannot define two comparators on the same type
+Lowering error in Opaque::comparator_static: Comparator must be non-static
+Lowering error in Opaque::comparator_none: Comparator must have single parameter
+Lowering error in Opaque::comparator_none: Cannot define two comparators on the same type
+Lowering error in Opaque::comparator_othertype: Cannot define two comparators on the same type
+Lowering error in Opaque::comparator_othertype: Comparator must be non-static
+Lowering error in Opaque::comparator_badreturn: Cannot define two comparators on the same type
+Lowering error in Opaque::comparator_badreturn: Found comparison method that does not return cmp::Ordering
+Lowering error in Opaque::comparison_correct: Cannot define two comparators on the same type
+Lowering error in Opaque::ordering_wrong: Found cmp::Ordering in parameter or struct field, it is only allowed in return types
+Lowering error in Opaque::comparison_mut: Cannot define two comparators on the same type
+Lowering error in Opaque::comparison_mut: comparators must accept immutable parameters
+Lowering error in Opaque::comparison_opt: Cannot define two comparators on the same type
+Lowering error in Opaque::comparison_opt: comparators must accept non-optional parameters
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__iterator.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__iterator.snap
new file mode 100644
index 0000000..8cb0929
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__iterator.snap
@@ -0,0 +1,13 @@
+---
+source: core/src/hir/attrs.rs
+expression: output
+---
+Lowering error in Broken::iterable_no_return: Iterables must return a custom type
+Lowering error in Broken::iterable_no_self: Iterables must take self
+Lowering error in Broken::iterable_non_custom: Cannot mark type as iterable twice
+Lowering error in Broken::iterable_non_custom: Iterables must return a custom opaque type
+Lowering error in BrokenIterator::iterator_no_return: Iterator method must return nullable value
+Lowering error in BrokenIterator::iterator_no_self: Iterators must take self
+Lowering error in BrokenIterator::iterator_no_option: Cannot mark type as iterator twice
+Lowering error in BrokenIterator::iterator_no_option: Iterator method must return nullable value
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__unsupported_features.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__unsupported_features.snap
new file mode 100644
index 0000000..f66b609
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__attrs__tests__unsupported_features.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/hir/attrs.rs
+expression: output
+---
+Lowering error in OutStruct: Options of structs/enums/primitives not supported by this backend
+Lowering error in Struct: Options of structs/enums/primitives not supported by this backend
+Lowering error in Struct2: Options of structs/enums/primitives not supported by this backend
+Lowering error in Opaque::take_option: Options of structs/enums/primitives not supported by this backend
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__borrowing_fields.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__borrowing_fields.snap
new file mode 100644
index 0000000..4728442
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__borrowing_fields.snap
@@ -0,0 +1,25 @@
+---
+source: core/src/hir/elision.rs
+expression: lt_to_borrowing_fields
+---
+{
+ Static: [
+ "this.name",
+ "_s",
+ ],
+ NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ): [
+ "this.p_data",
+ ],
+ NonStatic(
+ Lifetime(
+ 1,
+ ),
+ ): [
+ "this.q_data",
+ "this.inner.more_data",
+ ],
+}
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__elision_in_struct.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__elision_in_struct.snap
new file mode 100644
index 0000000..3fec47a
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__elision_in_struct.snap
@@ -0,0 +1,137 @@
+---
+source: core/src/hir/elision.rs
+expression: method
+---
+Method {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "elided",
+ abi_name: "Opaque_elided",
+ lifetime_env: LifetimeEnv {
+ nodes: [],
+ num_lifetimes: 3,
+ },
+ param_self: Some(
+ ParamSelf {
+ ty: Opaque(
+ OpaquePath {
+ lifetimes: Lifetimes {
+ indices: [],
+ },
+ optional: NonOptional,
+ owner: Borrow {
+ lifetime: NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ),
+ mutability: Immutable,
+ },
+ tcx_id: OpaqueId(
+ 0,
+ ),
+ },
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ),
+ params: [
+ Param {
+ name: "x",
+ ty: Opaque(
+ OpaquePath {
+ lifetimes: Lifetimes {
+ indices: [
+ NonStatic(
+ Lifetime(
+ 2,
+ ),
+ ),
+ ],
+ },
+ optional: Optional(
+ false,
+ ),
+ owner: Borrow {
+ lifetime: NonStatic(
+ Lifetime(
+ 1,
+ ),
+ ),
+ mutability: Immutable,
+ },
+ tcx_id: OpaqueId(
+ 1,
+ ),
+ },
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ output: Infallible(
+ Unit,
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+}
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__simple_mod.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__simple_mod.snap
new file mode 100644
index 0000000..a19cedd
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__elision__tests__simple_mod.snap
@@ -0,0 +1,475 @@
+---
+source: core/src/hir/elision.rs
+expression: tcx
+---
+TypeContext {
+ out_structs: [
+ StructDef {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "OutStruct",
+ fields: [
+ StructField {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "inner",
+ ty: Opaque(
+ OpaquePath {
+ lifetimes: Lifetimes {
+ indices: [
+ NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ),
+ ],
+ },
+ optional: Optional(
+ false,
+ ),
+ owner: Own,
+ tcx_id: OpaqueId(
+ 0,
+ ),
+ },
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ methods: [
+ Method {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "new",
+ abi_name: "OutStruct_new",
+ lifetime_env: LifetimeEnv {
+ nodes: [
+ BoundedLifetime {
+ ident: "a",
+ longer: [],
+ shorter: [],
+ },
+ ],
+ num_lifetimes: 1,
+ },
+ param_self: None,
+ params: [
+ Param {
+ name: "s",
+ ty: Slice(
+ Str(
+ Some(
+ NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ),
+ ),
+ UnvalidatedUtf8,
+ ),
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ output: Infallible(
+ OutType(
+ Struct(
+ OutStruct(
+ StructPath {
+ lifetimes: Lifetimes {
+ indices: [
+ NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ),
+ ],
+ },
+ tcx_id: OutStructId(
+ 0,
+ ),
+ },
+ ),
+ ),
+ ),
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ lifetimes: LifetimeEnv {
+ nodes: [
+ BoundedLifetime {
+ ident: "a",
+ longer: [],
+ shorter: [],
+ },
+ ],
+ num_lifetimes: 1,
+ },
+ special_method_presence: SpecialMethodPresence {
+ comparator: false,
+ iterator: None,
+ iterable: None,
+ },
+ },
+ ],
+ structs: [
+ StructDef {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "Struct",
+ fields: [
+ StructField {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "s",
+ ty: Slice(
+ Str(
+ Some(
+ NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ),
+ ),
+ UnvalidatedUtf8,
+ ),
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ methods: [
+ Method {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "rustc_elision",
+ abi_name: "Struct_rustc_elision",
+ lifetime_env: LifetimeEnv {
+ nodes: [
+ BoundedLifetime {
+ ident: "a",
+ longer: [],
+ shorter: [],
+ },
+ ],
+ num_lifetimes: 2,
+ },
+ param_self: Some(
+ ParamSelf {
+ ty: Struct(
+ StructPath {
+ lifetimes: Lifetimes {
+ indices: [
+ NonStatic(
+ Lifetime(
+ 0,
+ ),
+ ),
+ ],
+ },
+ tcx_id: StructId(
+ 0,
+ ),
+ },
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ),
+ params: [
+ Param {
+ name: "s",
+ ty: Slice(
+ Str(
+ Some(
+ NonStatic(
+ Lifetime(
+ 1,
+ ),
+ ),
+ ),
+ UnvalidatedUtf8,
+ ),
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ output: Infallible(
+ OutType(
+ Slice(
+ Str(
+ Some(
+ NonStatic(
+ Lifetime(
+ 1,
+ ),
+ ),
+ ),
+ UnvalidatedUtf8,
+ ),
+ ),
+ ),
+ ),
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ },
+ ],
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ lifetimes: LifetimeEnv {
+ nodes: [
+ BoundedLifetime {
+ ident: "a",
+ longer: [],
+ shorter: [],
+ },
+ ],
+ num_lifetimes: 1,
+ },
+ special_method_presence: SpecialMethodPresence {
+ comparator: false,
+ iterator: None,
+ iterable: None,
+ },
+ },
+ ],
+ opaques: [
+ OpaqueDef {
+ docs: Docs(
+ "",
+ [],
+ ),
+ name: "Opaque",
+ methods: [],
+ attrs: Attrs {
+ disable: false,
+ namespace: None,
+ rename: RenameAttr {
+ pattern: None,
+ },
+ abi_rename: RenameAttr {
+ pattern: None,
+ },
+ special_method: None,
+ demo_attrs: DemoInfo {
+ generate: false,
+ default_constructor: false,
+ external: false,
+ custom_func: None,
+ input_cfg: DemoInputCFG {
+ label: "",
+ default_value: "",
+ },
+ },
+ },
+ lifetimes: LifetimeEnv {
+ nodes: [
+ BoundedLifetime {
+ ident: "a",
+ longer: [],
+ shorter: [],
+ },
+ ],
+ num_lifetimes: 1,
+ },
+ special_method_presence: SpecialMethodPresence {
+ comparator: false,
+ iterator: None,
+ iterable: None,
+ },
+ dtor_abi_name: "Opaque_destroy",
+ },
+ ],
+ enums: [],
+ traits: [],
+}
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__basic_lowering.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__basic_lowering.snap
new file mode 100644
index 0000000..03be000
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__basic_lowering.snap
@@ -0,0 +1,16 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in BadStructFields: Found FFI-unsafe type Option<u8> in struct field BadStructFields.field1, consider using DiplomatOption<u8>
+Lowering error in BadStructFields: Found FFI-unsafe type Result<u8, u8> in struct field BadStructFields.field2, consider using Result<u8, u8>
+Lowering error in BadStructFields: Results can only appear as the top-level return type of methods
+Lowering error in InStructWithOutField: found Box<T> in input where T is an opaque, but owned opaques aren't allowed in inputs. try &T instead? T = OtherOpaque
+Lowering error in InStructWithOutField: found struct in input that is marked with #[diplomat::out]: OutStruct in OutStruct
+Lowering error in Opaque::use_foo_ref: found &T in input where T is a custom type, but not opaque. T = Foo
+Lowering error in Opaque::return_foo_box: found Box<T> in output where T is a custom type but not opaque. non-opaques can't be behind pointers. T = Foo
+Lowering error in Opaque::use_self: Method `Opaque_use_self` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs
+Lowering error in Opaque::return_self: Method `Opaque_return_self` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs
+Lowering error in Opaque::use_opaque_owned: Opaque passed by value: OtherOpaque
+Lowering error in Opaque::return_opaque_owned: Opaque passed by value in input: OtherOpaque
+Lowering error in Opaque::use_out_as_in: found struct in input that is marked with #[diplomat::out]: OutStruct in OutStruct
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__lifetime_in_return.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__lifetime_in_return.snap
new file mode 100644
index 0000000..f052d18
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__lifetime_in_return.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in Opaque::returns_self: Found elided lifetime in return type, please explicitly specify
+Lowering error in Opaque::returns_foo: Found elided lifetime in return type, please explicitly specify
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__non_opaque_move.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__non_opaque_move.snap
new file mode 100644
index 0000000..283f6b1
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__non_opaque_move.snap
@@ -0,0 +1,9 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in NonOpaque::foo: Method `NonOpaque_foo` takes a reference to a struct as a self parameter, which isn't allowed
+Lowering error in Opaque::bar: found &T in output where T is a custom type, but not opaque. T = NonOpaque
+Lowering error in Opaque::baz: found &T in input where T is a custom type, but not opaque. T = NonOpaque
+Lowering error in Opaque::quux: found Box<T> in output where T is a custom type but not opaque. non-opaques can't be behind pointers. T = NonOpaque
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_error.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_error.snap
new file mode 100644
index 0000000..76bf3f0
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_error.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in OpaqueStruct::new: Opaque passed by value in input: OpaqueStruct
+Lowering error in OpaqueStruct::get_i32: Method `OpaqueStruct_get_i32` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_safe_use.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_safe_use.snap
new file mode 100644
index 0000000..ea2e6fc
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_checks_with_safe_use.snap
@@ -0,0 +1,5 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in NonOpaqueStruct: Methods on ZST structs are not yet implemented: NonOpaqueStruct
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap
new file mode 100644
index 0000000..1d470de
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in MyOpaqueStruct::new_broken: Opaque passed by value in input: MyOpaqueStruct
+Lowering error in MyOpaqueStruct::do_thing_broken: Method `MyOpaqueStruct_do_thing_broken` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option.snap
new file mode 100644
index 0000000..12937b2
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in BrokenStruct: Found FFI-unsafe type Option<u8> in struct field BrokenStruct.regular_option, consider using DiplomatOption<u8>
+Lowering error in BrokenStruct: Found FFI-unsafe type Option<CustomStruct> in struct field BrokenStruct.regular_option, consider using DiplomatOption<CustomStruct>
+Lowering error in Foo::diplo_option_ref: found DiplomatOption<&T>, please use Option<&T> (DiplomatOption is for primitives, structs, and enums)
+Lowering error in Foo::diplo_option_box: found DiplomatOption<Box<T>>, please use Option<Box<T>> (DiplomatOption is for primitives, structs, and enums)
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option_invalid.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option_invalid.snap
new file mode 100644
index 0000000..f6fc63b
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option_invalid.snap
@@ -0,0 +1,7 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in Foo: Found FFI-unsafe type Option<u8> in struct field Foo.field, consider using DiplomatOption<u8>
+Lowering error in Foo::do_thing: found Option<T> in input, where T isn't a reference but Option<T> in inputs requires that T is a reference to an opaque. T = Option<u16>
+Lowering error in Foo::do_thing2: Results can only appear as the top-level return type of methods
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option_valid.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option_valid.snap
new file mode 100644
index 0000000..fbd4d2c
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__option_valid.snap
@@ -0,0 +1,8 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in Foo: found Option<Box<T>> in input, but box isn't allowed in inputs. T = u8
+Lowering error in Foo::do_thing: found Option<Box<T>> in input, but box isn't allowed in inputs. T = u32
+Lowering error in Foo::do_thing2: found Option<&T> in input, but T isn't a custom type and therefore not opaque. T = u32
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__required_implied_bounds.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__required_implied_bounds.snap
new file mode 100644
index 0000000..d57f9ea
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__required_implied_bounds.snap
@@ -0,0 +1,11 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in Opaque::use_foo: Method should explicitly include this lifetime bound from param foo: 'y: 'x (comes from source type's 'b: 'a)
+Lowering error in Opaque::use_foo: Method should explicitly include this lifetime bound from param foo: 'z: 'y (comes from source type's 'c: 'b)
+Lowering error in Opaque::return_foo: Method should explicitly include this lifetime bound from return type: 'y: 'x (comes from source type's 'b: 'a)
+Lowering error in Opaque::return_foo: Method should explicitly include this lifetime bound from return type: 'z: 'y (comes from source type's 'c: 'b)
+Lowering error in Opaque::return_result_foo: Method should explicitly include this lifetime bound from return type: 'y: 'x (comes from source type's 'b: 'a)
+Lowering error in Opaque::return_result_foo: Method should explicitly include this lifetime bound from return type: 'z: 'y (comes from source type's 'c: 'b)
+
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__struct_forbidden.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__struct_forbidden.snap
new file mode 100644
index 0000000..41ee009
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__struct_forbidden.snap
@@ -0,0 +1,10 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in Crimes: Found FFI-unsafe type &'a str in struct field Crimes.slice1, consider using DiplomatUtf8Str<'a>
+Lowering error in Crimes: Found FFI-unsafe type &'a DiplomatStr in struct field Crimes.slice1, consider using DiplomatStrSlice<'a>
+Lowering error in Crimes: Found FFI-unsafe type &'a [u8] in struct field Crimes.slice2, consider using DiplomatSlice<'a,u8>
+Lowering error in Crimes: Found FFI-unsafe type Box<str> in struct field Crimes.slice3, consider using DiplomatOwnedUtf8Str
+Lowering error in Crimes: Found FFI-unsafe type Box<DiplomatStr> in struct field Crimes.slice3, consider using DiplomatOwnedStrSlice
+Lowering error in Crimes: Found FFI-unsafe type Box<[u8]> in struct field Crimes.slice4, consider using Box<[u8]>
diff --git a/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__zst_non_opaque.snap b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__zst_non_opaque.snap
new file mode 100644
index 0000000..b7c6e27
--- /dev/null
+++ b/crates/diplomat_core/src/hir/snapshots/diplomat_core__hir__type_context__tests__zst_non_opaque.snap
@@ -0,0 +1,5 @@
+---
+source: core/src/hir/type_context.rs
+expression: output
+---
+Lowering error in NonOpaque: Methods on ZST structs are not yet implemented: NonOpaque
diff --git a/crates/diplomat_core/src/hir/ty_position.rs b/crates/diplomat_core/src/hir/ty_position.rs
new file mode 100644
index 0000000..173f857
--- /dev/null
+++ b/crates/diplomat_core/src/hir/ty_position.rs
@@ -0,0 +1,326 @@
+use super::lifetimes::{Lifetime, Lifetimes, MaybeStatic};
+use super::{
+ Borrow, Callback, CallbackInstantiationFunctionality, LinkedLifetimes, MaybeOwn, Mutability,
+ NoCallback, NoTraitPath, OutStructId, ReturnableStructPath, StructDef, StructId, StructPath,
+ TraitId, TraitPath, TypeContext, TypeDef, TypeId,
+};
+use core::fmt::Debug;
+
+/// Abstraction over where a type can appear in a function signature.
+///
+/// # "Output only" and "everywhere" types
+///
+/// While Rust is able to give up ownership of values, languages that Diplomat
+/// supports (C++, Javascript, etc.) generally cannot. For example, we can
+/// construct a `Box<MyOpaque>` in a Rust function and _return_ it to the other
+/// language as a pointer. However, we cannot _accept_ `Box<MyOpaque>` as an input
+/// because there's nothing stopping other languages from using that value again.
+/// Therefore, we classify boxed opaques as "output only" types, since they can
+/// only be returned from Rust but not taken as inputs.
+///
+/// Furthermore, Diplomat also supports "bag o' stuff" structs where all fields get
+/// translated at the boundary. If one contains an "output only" type as a field,
+/// then the whole struct must also be "output only". In particular, this means
+/// that if a boxed opaque is nested in a bunch of "bag o' stuff" structs, than
+/// all of those structs must also be "output only".
+///
+/// Currently, there are only two classes of structs: those that are "output only",
+/// and those that are not. These are represented by the types [`OutputOnly`]
+/// and [`Everywhere`] marker types respectively, which are the _only_ two types
+/// that implement [`TyPosition`].
+///
+/// # How does abstraction help?
+///
+/// The HIR was designed around the idea of making invalid states unrepresentable.
+/// Since "output only" types can contain values that "everywhere" types cannot,
+/// it doesn't make sense for them to be represented in the same type, even if
+/// they're mostly the same. One of these differences is that opaques (which are
+/// always behind a pointer) can only be represented as a borrow in "everywhere"
+/// types, but can additionally be represented as owned in "output only" types.
+/// If we were to use the same type for both, then backends working with "everywhere"
+/// types would constantly have unreachable statements for owned opaque cases.
+///
+/// That being said, "output only" and "everywhere" types are still mostly the
+/// same, so this trait allows us to describe the differences. For example, the
+/// HIR uses a singular [`Type`](super::Type) type for representing both
+/// "output only" types and "everywhere" types, since it takes advantage of this
+/// traits associated types to "fill in" the different parts:
+/// ```ignore
+/// pub enum Type<P: TyPosition = Everywhere> {
+/// Primitive(PrimitiveType),
+/// Opaque(OpaquePath<Optional, P::OpaqueOwnership>),
+/// Struct(P::StructPath),
+/// Enum(EnumPath),
+/// Slice(Slice),
+/// }
+/// ```
+///
+/// When `P` takes on [`Everywhere`], this signature becomes:
+/// ```ignore
+/// pub enum Type {
+/// Primitive(PrimitiveType),
+/// Opaque(OpaquePath<Optional, Borrow>),
+/// Struct(StructPath),
+/// Enum(EnumPath),
+/// Slice(Slice),
+/// }
+/// ```
+///
+/// This allows us to represent any kind of type that can appear "everywhere"
+/// i.e. in inputs or outputs. Notice how the second generic in the `Opaque`
+/// variant becomes [`Borrow`]. This describes the semantics of the pointer that
+/// the opaque lives behind, and shows that for "everywhere" types, opaques
+/// can _only_ be represented as living behind a borrow.
+///
+/// Contrast this to when `P` takes on [`OutputOnly`]:
+/// ```ignore
+/// pub enum Type {
+/// Primitive(PrimitiveType),
+/// Opaque(OpaquePath<Optional, MaybeOwn>),
+/// Struct(OutStructPath),
+/// Enum(EnumPath),
+/// Slice(Slice),
+/// }
+/// ```
+/// Here, the second generic of the `Opaque` variant becomes [`MaybeOwn`], meaning
+/// that "output only" types can contain opaques that are either borrowed _or_ owned.
+///
+/// Therefore, this trait allows be extremely precise about making invalid states
+/// unrepresentable, while also reducing duplicated code.
+///
+pub trait TyPosition: Debug + Copy
+where
+ for<'tcx> TypeDef<'tcx>: From<&'tcx StructDef<Self>>,
+{
+ const IN_OUT_STATUS: InputOrOutput;
+ type CallbackInstantiation: Debug + CallbackInstantiationFunctionality;
+
+ /// Type representing how we can point to opaques, which must always be behind a pointer.
+ ///
+ /// The types represented by [`OutputOnly`] are capable of either owning or
+ /// borrowing opaques, and so the associated type for that impl is [`MaybeOwn`].
+ ///
+ /// On the other hand, types represented by [`Everywhere`] can only contain
+ /// borrowes, so the associated type for that impl is [`Borrow`].
+ type OpaqueOwnership: Debug + OpaqueOwner;
+
+ type StructId: Debug;
+
+ type StructPath: Debug + StructPathLike;
+
+ type TraitPath: Debug + TraitIdGetter;
+
+ fn wrap_struct_def<'tcx>(def: &'tcx StructDef<Self>) -> TypeDef<'tcx>;
+ fn build_callback(cb: Callback) -> Self::CallbackInstantiation;
+ fn build_trait_path(trait_path: TraitPath) -> Self::TraitPath;
+}
+
+/// Directionality of the type
+#[non_exhaustive]
+pub enum InputOrOutput {
+ Input,
+ Output,
+ InputOutput,
+}
+
+pub trait TraitIdGetter {
+ fn id(&self) -> TraitId;
+}
+
+/// One of 3 types implementing [`TyPosition`], representing types that can be
+/// used as both input and output to functions.
+///
+/// The restricted versions of this type are [`OutputOnly`] and [`InputOnly`].
+#[derive(Debug, Copy, Clone)]
+#[non_exhaustive]
+pub struct Everywhere;
+
+/// One of 3 types implementing [`TyPosition`], representing types that can
+/// only be used as return types in functions.
+///
+/// The directional opposite of this type is [`InputOnly`].
+#[derive(Debug, Copy, Clone)]
+#[non_exhaustive]
+pub struct OutputOnly;
+
+/// One of 3 types implementing [`TyPosition`], representing types that can
+/// only be used as input types in functions.
+///
+/// The directional opposite of this type is [`OutputOnly`].
+#[derive(Debug, Copy, Clone)]
+#[non_exhaustive]
+pub struct InputOnly;
+
+impl TyPosition for Everywhere {
+ const IN_OUT_STATUS: InputOrOutput = InputOrOutput::InputOutput;
+ type OpaqueOwnership = Borrow;
+ type StructId = StructId;
+ type StructPath = StructPath;
+ type CallbackInstantiation = NoCallback;
+ type TraitPath = NoTraitPath;
+
+ fn wrap_struct_def<'tcx>(def: &'tcx StructDef<Self>) -> TypeDef<'tcx> {
+ TypeDef::Struct(def)
+ }
+ fn build_callback(_cb: Callback) -> Self::CallbackInstantiation {
+ panic!("Callbacks must be input-only");
+ }
+ fn build_trait_path(_trait_path: TraitPath) -> Self::TraitPath {
+ panic!("Traits must be input-only");
+ }
+}
+
+impl TyPosition for OutputOnly {
+ const IN_OUT_STATUS: InputOrOutput = InputOrOutput::Output;
+ type OpaqueOwnership = MaybeOwn;
+ type StructId = OutStructId;
+ type StructPath = ReturnableStructPath;
+ type CallbackInstantiation = NoCallback;
+ type TraitPath = NoTraitPath;
+
+ fn wrap_struct_def<'tcx>(def: &'tcx StructDef<Self>) -> TypeDef<'tcx> {
+ TypeDef::OutStruct(def)
+ }
+ fn build_callback(_cb: Callback) -> Self::CallbackInstantiation {
+ panic!("Callbacks must be input-only");
+ }
+ fn build_trait_path(_trait_path: TraitPath) -> Self::TraitPath {
+ panic!("Traits must be input-only");
+ }
+}
+
+impl TyPosition for InputOnly {
+ const IN_OUT_STATUS: InputOrOutput = InputOrOutput::Input;
+ type OpaqueOwnership = Borrow;
+ type StructId = StructId;
+ type StructPath = StructPath;
+ type CallbackInstantiation = Callback;
+ type TraitPath = TraitPath;
+
+ fn wrap_struct_def<'tcx>(_def: &'tcx StructDef<Self>) -> TypeDef<'tcx> {
+ panic!("Input-only structs are not currently supported");
+ }
+ fn build_callback(cb: Callback) -> Self::CallbackInstantiation {
+ cb
+ }
+ fn build_trait_path(trait_path: TraitPath) -> Self::TraitPath {
+ trait_path
+ }
+}
+
+pub trait StructPathLike {
+ fn lifetimes(&self) -> &Lifetimes;
+ fn id(&self) -> TypeId;
+
+ /// Get a map of lifetimes used on this path to lifetimes as named in the def site. See [`LinkedLifetimes`]
+ /// for more information.
+ fn link_lifetimes<'def, 'tcx>(
+ &'def self,
+ tcx: &'tcx TypeContext,
+ ) -> LinkedLifetimes<'def, 'tcx>;
+}
+
+impl StructPathLike for StructPath {
+ fn lifetimes(&self) -> &Lifetimes {
+ &self.lifetimes
+ }
+ fn id(&self) -> TypeId {
+ self.tcx_id.into()
+ }
+
+ fn link_lifetimes<'def, 'tcx>(
+ &'def self,
+ tcx: &'tcx TypeContext,
+ ) -> LinkedLifetimes<'def, 'tcx> {
+ let struc = self.resolve(tcx);
+ let env = &struc.lifetimes;
+ LinkedLifetimes::new(env, None, &self.lifetimes)
+ }
+}
+
+impl StructPathLike for ReturnableStructPath {
+ fn lifetimes(&self) -> &Lifetimes {
+ self.lifetimes()
+ }
+ fn id(&self) -> TypeId {
+ match self {
+ ReturnableStructPath::Struct(p) => p.tcx_id.into(),
+ ReturnableStructPath::OutStruct(p) => p.tcx_id.into(),
+ }
+ }
+
+ fn link_lifetimes<'def, 'tcx>(
+ &'def self,
+ tcx: &'tcx TypeContext,
+ ) -> LinkedLifetimes<'def, 'tcx> {
+ match self {
+ Self::Struct(p) => p.link_lifetimes(tcx),
+ Self::OutStruct(p) => p.link_lifetimes(tcx),
+ }
+ }
+}
+
+impl TraitIdGetter for TraitPath {
+ fn id(&self) -> TraitId {
+ self.tcx_id
+ }
+}
+
+impl TraitIdGetter for NoTraitPath {
+ fn id(&self) -> TraitId {
+ panic!("Trait path not allowed here, no trait ID valid");
+ }
+}
+
+/// Abstraction over how a type can hold a pointer to an opaque.
+///
+/// This trait is designed as a helper abstraction for the `OpaqueOwnership`
+/// associated type in the [`TyPosition`] trait. As such, only has two implementing
+/// types: [`MaybeOwn`] and [`Borrow`] for the [`OutputOnly`] and [`Everywhere`]
+/// implementations of [`TyPosition`] respectively.
+pub trait OpaqueOwner {
+ /// Return the mutability of this owner
+ fn mutability(&self) -> Option<Mutability>;
+
+ fn is_owned(&self) -> bool;
+
+ /// Return the lifetime of the borrow, if any.
+ fn lifetime(&self) -> Option<MaybeStatic<Lifetime>>;
+}
+
+impl OpaqueOwner for MaybeOwn {
+ fn mutability(&self) -> Option<Mutability> {
+ match self {
+ MaybeOwn::Own => None,
+ MaybeOwn::Borrow(b) => b.mutability(),
+ }
+ }
+
+ fn is_owned(&self) -> bool {
+ match self {
+ MaybeOwn::Own => true,
+ MaybeOwn::Borrow(_) => false,
+ }
+ }
+
+ fn lifetime(&self) -> Option<MaybeStatic<Lifetime>> {
+ match self {
+ MaybeOwn::Own => None,
+ MaybeOwn::Borrow(b) => b.lifetime(),
+ }
+ }
+}
+
+impl OpaqueOwner for Borrow {
+ fn mutability(&self) -> Option<Mutability> {
+ Some(self.mutability)
+ }
+
+ fn is_owned(&self) -> bool {
+ false
+ }
+
+ fn lifetime(&self) -> Option<MaybeStatic<Lifetime>> {
+ Some(self.lifetime)
+ }
+}
diff --git a/crates/diplomat_core/src/hir/type_context.rs b/crates/diplomat_core/src/hir/type_context.rs
new file mode 100644
index 0000000..7472617
--- /dev/null
+++ b/crates/diplomat_core/src/hir/type_context.rs
@@ -0,0 +1,931 @@
+//! Store all the types contained in the HIR.
+
+use super::lowering::{ErrorAndContext, ErrorStore, ItemAndInfo};
+use super::ty_position::StructPathLike;
+use super::{
+ AttributeValidator, Attrs, EnumDef, LoweringContext, LoweringError, MaybeStatic, OpaqueDef,
+ OutStructDef, StructDef, TraitDef, TypeDef,
+};
+use crate::ast::attrs::AttrInheritContext;
+#[allow(unused_imports)] // use in docs links
+use crate::hir;
+use crate::{ast, Env};
+use core::fmt::{self, Display};
+use smallvec::SmallVec;
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::ops::Index;
+
+/// A context type owning all types exposed to Diplomat.
+#[derive(Debug)]
+pub struct TypeContext {
+ out_structs: Vec<OutStructDef>,
+ structs: Vec<StructDef>,
+ opaques: Vec<OpaqueDef>,
+ enums: Vec<EnumDef>,
+ traits: Vec<TraitDef>,
+}
+
+/// Key used to index into a [`TypeContext`] representing a struct.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct StructId(usize);
+
+/// Key used to index into a [`TypeContext`] representing an out struct.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct OutStructId(usize);
+
+/// Key used to index into a [`TypeContext`] representing a opaque.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct OpaqueId(usize);
+
+/// Key used to index into a [`TypeContext`] representing an enum.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct EnumId(usize);
+
+/// Key used to index into a [`TypeContext`] representing a trait.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct TraitId(usize);
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum TypeId {
+ Struct(StructId),
+ OutStruct(OutStructId),
+ Opaque(OpaqueId),
+ Enum(EnumId),
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum SymbolId {
+ TypeId(TypeId),
+ TraitId(TraitId),
+}
+
+enum Param<'a> {
+ Input(&'a str),
+ Return,
+}
+
+impl Display for Param<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Param::Input(s) = *self {
+ write!(f, "param {s}")
+ } else {
+ write!(f, "return type")
+ }
+ }
+}
+
+impl TypeContext {
+ pub fn all_types<'tcx>(&'tcx self) -> impl Iterator<Item = (TypeId, TypeDef<'tcx>)> {
+ self.structs
+ .iter()
+ .enumerate()
+ .map(|(i, ty)| (TypeId::Struct(StructId(i)), TypeDef::Struct(ty)))
+ .chain(
+ self.out_structs
+ .iter()
+ .enumerate()
+ .map(|(i, ty)| (TypeId::OutStruct(OutStructId(i)), TypeDef::OutStruct(ty))),
+ )
+ .chain(
+ self.opaques
+ .iter()
+ .enumerate()
+ .map(|(i, ty)| (TypeId::Opaque(OpaqueId(i)), TypeDef::Opaque(ty))),
+ )
+ .chain(
+ self.enums
+ .iter()
+ .enumerate()
+ .map(|(i, ty)| (TypeId::Enum(EnumId(i)), TypeDef::Enum(ty))),
+ )
+ }
+
+ pub fn all_traits<'tcx>(&'tcx self) -> impl Iterator<Item = (TraitId, &TraitDef)> {
+ self.traits
+ .iter()
+ .enumerate()
+ .map(|(i, trt)| (TraitId(i), trt))
+ }
+
+ pub fn out_structs(&self) -> &[OutStructDef] {
+ &self.out_structs
+ }
+
+ pub fn structs(&self) -> &[StructDef] {
+ &self.structs
+ }
+
+ pub fn opaques(&self) -> &[OpaqueDef] {
+ &self.opaques
+ }
+
+ pub fn enums(&self) -> &[EnumDef] {
+ &self.enums
+ }
+
+ pub fn traits(&self) -> &[TraitDef] {
+ &self.traits
+ }
+
+ pub fn resolve_type<'tcx>(&'tcx self, id: TypeId) -> TypeDef<'tcx> {
+ match id {
+ TypeId::Struct(i) => TypeDef::Struct(self.resolve_struct(i)),
+ TypeId::OutStruct(i) => TypeDef::OutStruct(self.resolve_out_struct(i)),
+ TypeId::Opaque(i) => TypeDef::Opaque(self.resolve_opaque(i)),
+ TypeId::Enum(i) => TypeDef::Enum(self.resolve_enum(i)),
+ }
+ }
+
+ /// Helper methods for resolving different IDs.
+ ///
+ /// Prefer using `resolve_type()` for simplicity.
+ pub fn resolve_out_struct(&self, id: OutStructId) -> &OutStructDef {
+ self.out_structs.index(id.0)
+ }
+
+ /// Helper methods for resolving different IDs.
+ ///
+ /// Prefer using `resolve_type()` for simplicity.
+ pub fn resolve_struct(&self, id: StructId) -> &StructDef {
+ self.structs.index(id.0)
+ }
+
+ /// Helper methods for resolving different IDs.
+ ///
+ /// Prefer using `resolve_type()` for simplicity.
+ pub fn resolve_opaque(&self, id: OpaqueId) -> &OpaqueDef {
+ self.opaques.index(id.0)
+ }
+
+ /// Helper methods for resolving different IDs.
+ ///
+ /// Prefer using `resolve_type()` for simplicity.
+ pub fn resolve_enum(&self, id: EnumId) -> &EnumDef {
+ self.enums.index(id.0)
+ }
+
+ pub fn resolve_trait(&self, id: TraitId) -> &TraitDef {
+ self.traits.index(id.0)
+ }
+
+ /// Resolve and format a named type for use in diagnostics
+ /// (don't apply rename rules and such)
+ pub fn fmt_type_name_diagnostics(&self, id: TypeId) -> Cow<str> {
+ self.resolve_type(id).name().as_str().into()
+ }
+
+ pub fn fmt_symbol_name_diagnostics(&self, id: SymbolId) -> Cow<str> {
+ match id {
+ SymbolId::TypeId(id) => self.fmt_type_name_diagnostics(id),
+ SymbolId::TraitId(id) => self.resolve_trait(id).name.as_str().into(),
+ }
+ }
+
+ /// Lower the AST to the HIR while simultaneously performing validation.
+ pub fn from_syn<'ast>(
+ s: &'ast syn::File,
+ attr_validator: impl AttributeValidator + 'static,
+ ) -> Result<Self, Vec<ErrorAndContext>> {
+ let types = ast::File::from(s).all_types();
+ let (mut ctx, hir) = Self::from_ast_without_validation(&types, attr_validator)?;
+ ctx.errors.set_item("(validation)");
+ hir.validate(&mut ctx.errors);
+ if !ctx.errors.is_empty() {
+ return Err(ctx.errors.take_errors());
+ }
+ Ok(hir)
+ }
+
+ /// Lower the AST to the HIR, without validation. For testing
+ pub(super) fn from_ast_without_validation<'ast>(
+ env: &'ast Env,
+ attr_validator: impl AttributeValidator + 'static,
+ ) -> Result<(LoweringContext, Self), Vec<ErrorAndContext>> {
+ let mut ast_out_structs = SmallVec::<[_; 16]>::new();
+ let mut ast_structs = SmallVec::<[_; 16]>::new();
+ let mut ast_opaques = SmallVec::<[_; 16]>::new();
+ let mut ast_enums = SmallVec::<[_; 16]>::new();
+ let mut ast_traits = SmallVec::<[_; 16]>::new();
+
+ let mut errors = ErrorStore::default();
+
+ for (path, mod_env) in env.iter_modules() {
+ errors.set_item(
+ path.elements
+ .last()
+ .map(|m| m.as_str())
+ .unwrap_or("root module"),
+ );
+ let mod_attrs = Attrs::from_ast(
+ &mod_env.attrs,
+ &attr_validator,
+ &Default::default(),
+ &mut errors,
+ );
+ let ty_attrs = mod_attrs.for_inheritance(AttrInheritContext::Type);
+ let method_attrs =
+ mod_attrs.for_inheritance(AttrInheritContext::MethodOrImplFromModule);
+
+ for sym in mod_env.items() {
+ match sym {
+ ast::ModSymbol::CustomType(custom_type) => match custom_type {
+ ast::CustomType::Struct(strct) => {
+ let id = if strct.output_only {
+ TypeId::OutStruct(OutStructId(ast_out_structs.len()))
+ } else {
+ TypeId::Struct(StructId(ast_structs.len()))
+ };
+ let item = ItemAndInfo {
+ item: strct,
+ in_path: path,
+ ty_parent_attrs: ty_attrs.clone(),
+ method_parent_attrs: method_attrs.clone(),
+ id: id.into(),
+ };
+ if strct.output_only {
+ ast_out_structs.push(item);
+ } else {
+ ast_structs.push(item);
+ }
+ }
+ ast::CustomType::Opaque(opaque) => {
+ let item = ItemAndInfo {
+ item: opaque,
+ in_path: path,
+ ty_parent_attrs: ty_attrs.clone(),
+ method_parent_attrs: method_attrs.clone(),
+ id: TypeId::Opaque(OpaqueId(ast_opaques.len())).into(),
+ };
+ ast_opaques.push(item)
+ }
+ ast::CustomType::Enum(enm) => {
+ let item = ItemAndInfo {
+ item: enm,
+ in_path: path,
+ ty_parent_attrs: ty_attrs.clone(),
+ method_parent_attrs: method_attrs.clone(),
+ id: TypeId::Enum(EnumId(ast_enums.len())).into(),
+ };
+ ast_enums.push(item)
+ }
+ },
+ ast::ModSymbol::Trait(trt) => {
+ let item = ItemAndInfo {
+ item: trt,
+ in_path: path,
+ ty_parent_attrs: ty_attrs.clone(),
+ method_parent_attrs: method_attrs.clone(),
+ id: TraitId(ast_traits.len()).into(),
+ };
+ ast_traits.push(item)
+ }
+ _ => {}
+ }
+ }
+ }
+
+ let lookup_id = LookupId::new(
+ &ast_out_structs[..],
+ &ast_structs[..],
+ &ast_opaques[..],
+ &ast_enums[..],
+ &ast_traits[..],
+ );
+ let attr_validator = Box::new(attr_validator);
+
+ let mut ctx = LoweringContext {
+ lookup_id,
+ env,
+ errors,
+ attr_validator,
+ };
+
+ let out_structs = ctx.lower_all_out_structs(ast_out_structs.into_iter());
+ let structs = ctx.lower_all_structs(ast_structs.into_iter());
+ let opaques = ctx.lower_all_opaques(ast_opaques.into_iter());
+ let enums = ctx.lower_all_enums(ast_enums.into_iter());
+ let traits = ctx.lower_all_traits(ast_traits.into_iter()).unwrap();
+
+ match (out_structs, structs, opaques, enums) {
+ (Ok(out_structs), Ok(structs), Ok(opaques), Ok(enums)) => {
+ let res = Self {
+ out_structs,
+ structs,
+ opaques,
+ enums,
+ traits,
+ };
+
+ if !ctx.errors.is_empty() {
+ return Err(ctx.errors.take_errors());
+ }
+ Ok((ctx, res))
+ }
+ _ => {
+ assert!(
+ !ctx.errors.is_empty(),
+ "Lowering failed without error messages"
+ );
+ Err(ctx.errors.take_errors())
+ }
+ }
+ }
+
+ /// Run validation phase
+ ///
+ /// Currently validates that methods are not inheriting any transitive bounds from parameters
+ /// Todo: Automatically insert these bounds during HIR construction in a second phase
+ fn validate<'hir>(&'hir self, errors: &mut ErrorStore<'hir>) {
+ // Lifetime validity check
+ for (_id, ty) in self.all_types() {
+ errors.set_item(ty.name().as_str());
+ for method in ty.methods() {
+ errors.set_subitem(method.name.as_str());
+
+ // This check must occur before validate_ty_in_method is called
+ // since validate_ty_in_method calls link_lifetimes which does not
+ // work for structs with elision
+ let mut failed = false;
+ method.output.with_contained_types(|out_ty| {
+ for lt in out_ty.lifetimes() {
+ if let MaybeStatic::NonStatic(lt) = lt {
+ if method.lifetime_env.get_bounds(lt).is_none() {
+ errors.push(LoweringError::Other(
+ "Found elided lifetime in return type, please explicitly specify".into(),
+ ));
+
+ failed = true;
+ break;
+ }
+ }
+ }
+ });
+
+ if failed {
+ // link_lifetimes will fail if elision exists
+ continue;
+ }
+
+ for param in &method.params {
+ self.validate_ty_in_method(
+ errors,
+ Param::Input(param.name.as_str()),
+ ¶m.ty,
+ method,
+ )
+ }
+
+ method.output.with_contained_types(|out_ty| {
+ self.validate_ty_in_method(errors, Param::Return, out_ty, method);
+ })
+ }
+ }
+ }
+
+ /// Ensure that a given method's input our output type does not implicitly introduce bounds that are not
+ /// already specified on the method
+ fn validate_ty_in_method<P: hir::TyPosition>(
+ &self,
+ errors: &mut ErrorStore,
+ param: Param,
+ param_ty: &hir::Type<P>,
+ method: &hir::Method,
+ ) {
+ let linked = match ¶m_ty {
+ hir::Type::Opaque(p) => p.link_lifetimes(self),
+ hir::Type::Struct(p) => p.link_lifetimes(self),
+ _ => return,
+ };
+
+ for (use_lt, def_lt) in linked.lifetimes_all() {
+ let MaybeStatic::NonStatic(use_lt) = use_lt else {
+ continue;
+ };
+ let Some(use_bounds) = &method.lifetime_env.get_bounds(use_lt) else {
+ continue;
+ };
+ let use_longer_lifetimes = &use_bounds.longer;
+ let anchor;
+ let def_longer_lifetimes = if let Some(def_lt) = def_lt {
+ let Some(def_bounds) = &linked.def_env().get_bounds(def_lt) else {
+ continue;
+ };
+ &def_bounds.longer
+ } else {
+ anchor = linked.def_env().all_lifetimes().collect();
+ &anchor
+ };
+
+ for def_longer in def_longer_lifetimes {
+ let MaybeStatic::NonStatic(corresponding_use) = linked.def_to_use(*def_longer)
+ else {
+ continue;
+ };
+
+ // In the case of stuff like <'a, 'a> passed to Foo<'x, 'y: 'x> the bound
+ // is trivially fulfilled
+ if corresponding_use == use_lt {
+ continue;
+ }
+
+ if !use_longer_lifetimes.contains(&corresponding_use) {
+ let use_name = method.lifetime_env.fmt_lifetime(use_lt);
+ let use_longer_name = method.lifetime_env.fmt_lifetime(corresponding_use);
+ let def_cause = if let Some(def_lt) = def_lt {
+ let def_name = linked.def_env().fmt_lifetime(def_lt);
+ let def_longer_name = linked.def_env().fmt_lifetime(def_longer);
+ format!("comes from source type's '{def_longer_name}: '{def_name}")
+ } else {
+ // This case is technically already handled in the lifetime lowerer, we're being careful
+ "comes from &-ref's lifetime in parameter".into()
+ };
+ errors.push(LoweringError::Other(format!("Method should explicitly include this \
+ lifetime bound from {param}: '{use_longer_name}: '{use_name} ({def_cause})")))
+ }
+ }
+ }
+ }
+}
+
+/// Struct that just wraps the mapping from AST custom types to their IDs that
+/// will show up in the final [`TypeContext`].
+///
+/// The entire point of this type is to reduce the number of arguments in helper
+/// functions which need to look up IDs for structs. It does nothing fancy and
+/// is only ever used when constructing a [`TypeContext`].
+pub(super) struct LookupId<'ast> {
+ out_struct_map: HashMap<&'ast ast::Struct, OutStructId>,
+ struct_map: HashMap<&'ast ast::Struct, StructId>,
+ opaque_map: HashMap<&'ast ast::OpaqueStruct, OpaqueId>,
+ enum_map: HashMap<&'ast ast::Enum, EnumId>,
+ trait_map: HashMap<&'ast ast::Trait, TraitId>,
+}
+
+impl<'ast> LookupId<'ast> {
+ /// Returns a new [`LookupId`].
+ fn new(
+ out_structs: &[ItemAndInfo<'ast, ast::Struct>],
+ structs: &[ItemAndInfo<'ast, ast::Struct>],
+ opaques: &[ItemAndInfo<'ast, ast::OpaqueStruct>],
+ enums: &[ItemAndInfo<'ast, ast::Enum>],
+ traits: &[ItemAndInfo<'ast, ast::Trait>],
+ ) -> Self {
+ Self {
+ out_struct_map: out_structs
+ .iter()
+ .enumerate()
+ .map(|(index, item)| (item.item, OutStructId(index)))
+ .collect(),
+ struct_map: structs
+ .iter()
+ .enumerate()
+ .map(|(index, item)| (item.item, StructId(index)))
+ .collect(),
+ opaque_map: opaques
+ .iter()
+ .enumerate()
+ .map(|(index, item)| (item.item, OpaqueId(index)))
+ .collect(),
+ enum_map: enums
+ .iter()
+ .enumerate()
+ .map(|(index, item)| (item.item, EnumId(index)))
+ .collect(),
+ trait_map: traits
+ .iter()
+ .enumerate()
+ .map(|(index, item)| (item.item, TraitId(index)))
+ .collect(),
+ }
+ }
+
+ pub(super) fn resolve_out_struct(&self, strct: &ast::Struct) -> Option<OutStructId> {
+ self.out_struct_map.get(strct).copied()
+ }
+
+ pub(super) fn resolve_struct(&self, strct: &ast::Struct) -> Option<StructId> {
+ self.struct_map.get(strct).copied()
+ }
+
+ pub(super) fn resolve_opaque(&self, opaque: &ast::OpaqueStruct) -> Option<OpaqueId> {
+ self.opaque_map.get(opaque).copied()
+ }
+
+ pub(super) fn resolve_enum(&self, enm: &ast::Enum) -> Option<EnumId> {
+ self.enum_map.get(enm).copied()
+ }
+
+ pub(super) fn resolve_trait(&self, trt: &ast::Trait) -> Option<TraitId> {
+ self.trait_map.get(trt).copied()
+ }
+}
+
+impl From<StructId> for TypeId {
+ fn from(x: StructId) -> Self {
+ TypeId::Struct(x)
+ }
+}
+
+impl From<OutStructId> for TypeId {
+ fn from(x: OutStructId) -> Self {
+ TypeId::OutStruct(x)
+ }
+}
+
+impl From<OpaqueId> for TypeId {
+ fn from(x: OpaqueId) -> Self {
+ TypeId::Opaque(x)
+ }
+}
+
+impl From<EnumId> for TypeId {
+ fn from(x: EnumId) -> Self {
+ TypeId::Enum(x)
+ }
+}
+
+impl From<TypeId> for SymbolId {
+ fn from(x: TypeId) -> Self {
+ SymbolId::TypeId(x)
+ }
+}
+
+impl From<TraitId> for SymbolId {
+ fn from(x: TraitId) -> Self {
+ SymbolId::TraitId(x)
+ }
+}
+
+impl TryInto<TypeId> for SymbolId {
+ type Error = ();
+ fn try_into(self) -> Result<TypeId, Self::Error> {
+ match self {
+ SymbolId::TypeId(id) => Ok(id),
+ _ => Err(()),
+ }
+ }
+}
+
+impl TryInto<TraitId> for SymbolId {
+ type Error = ();
+ fn try_into(self) -> Result<TraitId, Self::Error> {
+ match self {
+ SymbolId::TraitId(id) => Ok(id),
+ _ => Err(()),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::hir;
+ use std::fmt::Write;
+
+ macro_rules! uitest_lowering {
+ ($($file:tt)*) => {
+ let parsed: syn::File = syn::parse_quote! { $($file)* };
+
+ let mut output = String::new();
+
+ let mut attr_validator = hir::BasicAttributeValidator::new("tests");
+ attr_validator.support.option = true;
+ match hir::TypeContext::from_syn(&parsed, attr_validator) {
+ Ok(_context) => (),
+ Err(e) => {
+ for (ctx, err) in e {
+ writeln!(&mut output, "Lowering error in {ctx}: {err}").unwrap();
+ }
+ }
+ };
+ insta::with_settings!({}, {
+ insta::assert_snapshot!(output)
+ });
+ }
+ }
+
+ #[test]
+ fn test_required_implied_bounds() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ #[diplomat::opaque]
+ struct Foo<'a, 'b: 'a, 'c: 'b> (&'a u8, &'b u8, &'c u8);
+
+ #[diplomat::opaque]
+ struct Opaque;
+
+
+ #[diplomat::opaque]
+ struct OneLifetime<'a>(&'a u8);
+
+ impl Opaque {
+ pub fn use_foo<'x, 'y, 'z>(&self, foo: &Foo<'x, 'y, 'z>) {}
+ pub fn return_foo<'x, 'y, 'z>(&'x self) -> Box<Foo<'x, 'y, 'z>> {}
+ pub fn return_result_foo<'x, 'y, 'z>(&'x self) -> Result<Box<Foo<'x, 'y, 'z>>, ()> {}
+ // This doesn't actually error since the lowerer inserts the implicit bound
+ pub fn implied_ref_bound<'a, 'b>(&self, one_lt: &'a OneLifetime<'b>) {}
+ }
+ }
+ }
+ }
+
+ /// This is a buch of tests put together
+ #[test]
+ fn test_basic_lowering() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod other_ffi {
+
+ struct Foo {
+ field: u8
+ }
+
+ #[diplomat::out]
+ struct OutStruct {
+ field: Box<OtherOpaque>,
+ }
+
+ #[diplomat::opaque]
+ struct OtherOpaque;
+ }
+ #[diplomat::bridge]
+ mod ffi {
+ use crate::other_ffi::{Foo, OutStruct, OtherOpaque};
+
+ #[diplomat::opaque]
+ struct Opaque;
+
+ struct EmptyStruct;
+
+ enum EmptyEnum {}
+
+ struct InStructWithOutField {
+ field: Box<OtherOpaque>,
+ out_struct: OutStruct,
+ }
+
+ struct BadStructFields {
+ field1: Option<u8>,
+ field2: Result<u8, u8>,
+ }
+
+ impl Opaque {
+ pub fn use_foo_ref(&self, foo: &Foo) {}
+ pub fn return_foo_box(&self) -> Box<Foo> {}
+ pub fn use_self(self) {}
+ pub fn return_self(self) -> Self {}
+ pub fn use_opaque_owned(&self, opaque: OtherOpaque) {}
+ pub fn return_opaque_owned(&self) -> OtherOpaque {}
+ pub fn use_out_as_in(&self, out: OutStruct) {}
+ }
+
+ }
+ }
+ }
+
+ #[test]
+ fn test_opaque_ffi() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ #[diplomat::opaque]
+ struct MyOpaqueStruct(UnknownType);
+
+ impl MyOpaqueStruct {
+ pub fn new() -> Box<MyOpaqueStruct> {}
+ pub fn new_broken() -> MyOpaqueStruct {}
+ pub fn do_thing(&self) {}
+ pub fn do_thing_broken(self) {}
+ pub fn broken_differently(&self, x: &MyOpaqueStruct) {}
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn opaque_checks_with_safe_use() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct NonOpaqueStruct {}
+
+ impl NonOpaqueStruct {
+ pub fn new(x: i32) -> NonOpaqueStruct {
+ unimplemented!();
+ }
+ }
+
+ #[diplomat::opaque]
+ struct OpaqueStruct {}
+
+ impl OpaqueStruct {
+ pub fn new() -> Box<OpaqueStruct> {
+ unimplemented!();
+ }
+
+ pub fn get_i32(&self) -> i32 {
+ unimplemented!()
+ }
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn opaque_checks_with_error() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ #[diplomat::opaque]
+ struct OpaqueStruct {}
+
+ impl OpaqueStruct {
+ pub fn new() -> OpaqueStruct {
+ unimplemented!();
+ }
+
+ pub fn get_i32(self) -> i32 {
+ unimplemented!()
+ }
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn zst_non_opaque() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct NonOpaque;
+
+ impl NonOpaque {
+ pub fn foo(self) {}
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn option_invalid() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ use diplomat_runtime::DiplomatResult;
+ struct Foo {
+ field: Option<u8>,
+ }
+
+ impl Foo {
+ pub fn do_thing(opt: Option<Option<u16>>) {
+
+ }
+
+ pub fn do_thing2(opt: DiplomatResult<Option<DiplomatChar>, u8>) {
+
+ }
+ pub fn do_thing2(opt: Option<u16>) {
+
+ }
+
+ pub fn do_thing3() -> Option<u16> {
+
+ }
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn option_valid() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct Foo {
+ field: Option<Box<u8>>,
+ }
+
+ impl Foo {
+ pub fn do_thing(opt: Option<Box<u32>>) {
+
+ }
+ pub fn do_thing2(opt: Option<&u32>) {
+
+ }
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn non_opaque_move() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct NonOpaque {
+ num: u8,
+ }
+
+ impl NonOpaque {
+ pub fn foo(&self) {}
+ }
+
+ #[diplomat::opaque]
+ struct Opaque;
+
+ impl Opaque {
+ pub fn bar<'a>(&'a self) -> &'a NonOpaque {}
+ pub fn baz<'a>(&'a self, x: &'a NonOpaque) {}
+ pub fn quux(&self) -> Box<NonOpaque> {}
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn test_lifetime_in_return() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ #[diplomat::opaque]
+ struct Opaque;
+
+ struct Foo<'a> {
+ x: &'a Opaque,
+ }
+
+ impl Opaque {
+ pub fn returns_self(&self) -> &Self {}
+ pub fn returns_foo(&self) -> Foo {}
+ }
+ }
+ };
+ }
+ #[test]
+ fn test_struct_forbidden() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ struct Crimes<'a> {
+ slice1: &'a str,
+ slice1: &'a DiplomatStr,
+ slice2: &'a [u8],
+ slice3: Box<str>,
+ slice3: Box<DiplomatStr>,
+ slice4: Box<[u8]>,
+ }
+ }
+ };
+ }
+
+ #[test]
+ fn test_option() {
+ uitest_lowering! {
+ #[diplomat::bridge]
+ mod ffi {
+ use diplomat_runtime::DiplomatOption;
+ #[diplomat::opaque]
+ struct Foo {}
+ struct CustomStruct {
+ num: u8,
+ b: bool,
+ diplo_option: DiplomatOption<u8>,
+ }
+
+ struct BrokenStruct {
+ regular_option: Option<u8>,
+ regular_option: Option<CustomStruct>,
+ }
+ impl Foo {
+ pub fn diplo_option_u8(x: DiplomatOption<u8>) -> DiplomatOption<u8> {
+ x
+ }
+ pub fn diplo_option_ref(x: DiplomatOption<&Foo>) -> DiplomatOption<&Foo> {
+ x
+ }
+ pub fn diplo_option_box() -> DiplomatOption<Box<Foo>> {
+ x
+ }
+ pub fn diplo_option_struct(x: DiplomatOption<CustomStruct>) -> DiplomatOption<CustomStruct> {
+ x
+ }
+ pub fn option_u8(x: Option<u8>) -> Option<u8> {
+ x
+ }
+ pub fn option_ref(x: Option<&Foo>) -> Option<&Foo> {
+ x
+ }
+ pub fn option_box() -> Option<Box<Foo>> {
+ x
+ }
+ pub fn option_struct(x: Option<CustomStruct>) -> Option<CustomStruct> {
+ x
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/crates/diplomat_core/src/hir/types.rs b/crates/diplomat_core/src/hir/types.rs
new file mode 100644
index 0000000..c8c01ef
--- /dev/null
+++ b/crates/diplomat_core/src/hir/types.rs
@@ -0,0 +1,209 @@
+//! Types that can be exposed in Diplomat APIs.
+
+use super::lifetimes::{Lifetime, MaybeStatic};
+use super::{
+ EnumPath, Everywhere, NonOptional, OpaqueOwner, OpaquePath, Optional, OutputOnly,
+ PrimitiveType, StructPath, StructPathLike, TyPosition, TypeContext, TypeId,
+};
+use crate::ast;
+pub use ast::Mutability;
+pub use ast::StringEncoding;
+use either::Either;
+
+/// Type that can only be used as an output.
+pub type OutType = Type<OutputOnly>;
+
+/// Type that may be used as input or output.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum Type<P: TyPosition = Everywhere> {
+ Primitive(PrimitiveType),
+ Opaque(OpaquePath<Optional, P::OpaqueOwnership>),
+ Struct(P::StructPath),
+ ImplTrait(P::TraitPath),
+ Enum(EnumPath),
+ Slice(Slice),
+ Callback(P::CallbackInstantiation), // only a Callback if P == InputOnly
+ /// `DiplomatOption<T>`, for a primitive, struct, or enum `T`.
+ ///
+ /// In some cases this can be specified as `Option<T>`, but under the hood it gets translated to
+ /// the DiplomatOption FFI-compatible union type.
+ ///
+ /// This is *different* from `Option<&T>` and `Option<Box<T>` for an opaque `T`: That is represented as
+ /// a nullable pointer.
+ ///
+ /// This does not get used when the user writes `-> Option<T>` (for non-opaque T):
+ /// that will always use [`ReturnType::Nullable`](crate::hir::ReturnType::Nullable).
+ DiplomatOption(Box<Type<P>>),
+}
+
+/// Type that can appear in the `self` position.
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum SelfType {
+ Opaque(OpaquePath<NonOptional, Borrow>),
+ Struct(StructPath),
+ Enum(EnumPath),
+}
+
+#[derive(Copy, Clone, Debug)]
+#[non_exhaustive]
+pub enum Slice {
+ /// A string slice, e.g. `&DiplomatStr` or `Box<DiplomatStr>`.
+ ///
+ /// Owned slices are useful for garbage-collected languages that have to
+ /// reallocate into non-gc memory anyway. For example for Dart it's more
+ /// efficient to accept `Box<str>` than to accept `&str` and then
+ /// allocate in Rust, as Dart will have to create the `Box<str`> to
+ /// pass `&str` anyway.
+ Str(Option<MaybeStatic<Lifetime>>, StringEncoding),
+
+ /// A primitive slice, e.g. `&mut [u8]` or `Box<[usize]>`.
+ ///
+ /// Owned slices are useful for garbage-collected languages that have to
+ /// reallocate into non-gc memory anyway. For example for Dart it's more
+ /// efficient to accept `Box<[bool]>` than to accept `&[bool]` and then
+ /// allocate in Rust, as Dart will have to create the `Box<[bool]`> to
+ /// pass `&[bool]` anyway.
+ Primitive(Option<Borrow>, PrimitiveType),
+
+ /// A `&[&DiplomatStr]]`. This type of slice always needs to be
+ /// allocated before passing it into Rust, as it has to conform to the
+ /// Rust ABI. In other languages this is the idiomatic list of string
+ /// views, i.e. `std::span<std::string_view>` or `core.List<core.String>`.
+ Strs(StringEncoding),
+}
+
+// For now, the lifetime in not optional. This is because when you have references
+// as fields of structs, the lifetime must always be present, and we want to uphold
+// this invariant at the type level within the HIR.
+//
+// The only time when a lifetime is optional in Rust code is in function signatures,
+// where implicit lifetimes are allowed. Getting this to all fit together will
+// involve getting the implicit lifetime thing to be understood by Diplomat, but
+// should be doable.
+#[derive(Copy, Clone, Debug)]
+#[non_exhaustive]
+pub struct Borrow {
+ pub lifetime: MaybeStatic<Lifetime>,
+ pub mutability: Mutability,
+}
+
+// This is implemented on InputOnly and Everywhere types. Could be extended
+// if we genericize .resolve() later.
+impl<P: TyPosition<StructPath = StructPath>> Type<P> {
+ /// Return the number of fields and leaves that will show up in the [`LifetimeTree`]
+ /// returned by [`Param::lifetime_tree`] and [`ParamSelf::lifetime_tree`].
+ ///
+ /// This method is used to calculate how much space to allocate upfront.
+ pub(super) fn field_leaf_lifetime_counts(&self, tcx: &TypeContext) -> (usize, usize) {
+ match self {
+ Type::Struct(ty) => ty.resolve(tcx).fields.iter().fold((1, 0), |acc, field| {
+ let inner = field.ty.field_leaf_lifetime_counts(tcx);
+ (acc.0 + inner.0, acc.1 + inner.1)
+ }),
+ Type::Opaque(_) | Type::Slice(_) | Type::Callback(_) | Type::ImplTrait(_) => (1, 1),
+ Type::Primitive(_) | Type::Enum(_) => (0, 0),
+ Type::DiplomatOption(ty) => ty.field_leaf_lifetime_counts(tcx),
+ }
+ }
+}
+
+impl<P: TyPosition> Type<P> {
+ /// Get all lifetimes "contained" in this type
+ pub fn lifetimes(&self) -> impl Iterator<Item = MaybeStatic<Lifetime>> + '_ {
+ match self {
+ Type::Opaque(opaque) => Either::Right(
+ opaque
+ .lifetimes
+ .as_slice()
+ .iter()
+ .copied()
+ .chain(opaque.owner.lifetime()),
+ ),
+ Type::Struct(struct_) => Either::Left(struct_.lifetimes().as_slice().iter().copied()),
+ Type::Slice(slice) => Either::Left(
+ slice
+ .lifetime()
+ .map(|lt| std::slice::from_ref(lt).iter().copied())
+ .unwrap_or([].iter().copied()),
+ ),
+ // TODO the Callback case
+ _ => Either::Left([].iter().copied()),
+ }
+ }
+
+ // For custom types, get the type id
+ pub fn id(&self) -> Option<TypeId> {
+ Some(match self {
+ Self::Opaque(p) => TypeId::Opaque(p.tcx_id),
+ Self::Enum(p) => TypeId::Enum(p.tcx_id),
+ Self::Struct(p) => p.id(),
+ _ => return None,
+ })
+ }
+
+ /// Unwrap to the inner type if `self` is `DiplomatOption`
+ pub fn unwrap_option(&self) -> &Type<P> {
+ match self {
+ Self::DiplomatOption(ref o) => o,
+ _ => self,
+ }
+ }
+
+ /// Whether this type is a `DiplomatOption` or optional Opaque
+ pub fn is_option(&self) -> bool {
+ match self {
+ Self::DiplomatOption(..) => true,
+ Self::Opaque(ref o) if o.is_optional() => true,
+ _ => false,
+ }
+ }
+}
+
+impl SelfType {
+ /// Returns whether the self parameter is borrowed immutably.
+ ///
+ /// Curently this can only happen with opaque types.
+ pub fn is_immutably_borrowed(&self) -> bool {
+ match self {
+ SelfType::Opaque(opaque_path) => opaque_path.owner.mutability == Mutability::Immutable,
+ _ => false,
+ }
+ }
+}
+
+impl Slice {
+ /// Returns the [`Lifetime`] contained in either the `Str` or `Primitive`
+ /// variant.
+ pub fn lifetime(&self) -> Option<&MaybeStatic<Lifetime>> {
+ match self {
+ Slice::Str(lifetime, ..) => lifetime.as_ref(),
+ Slice::Primitive(Some(reference), ..) => Some(&reference.lifetime),
+ Slice::Primitive(..) => None,
+ Slice::Strs(..) => Some({
+ const X: MaybeStatic<Lifetime> = MaybeStatic::NonStatic(Lifetime::new(usize::MAX));
+ &X
+ }),
+ }
+ }
+}
+
+impl Borrow {
+ pub(super) fn new(lifetime: MaybeStatic<Lifetime>, mutability: Mutability) -> Self {
+ Self {
+ lifetime,
+ mutability,
+ }
+ }
+}
+
+impl From<SelfType> for Type {
+ fn from(s: SelfType) -> Type {
+ match s {
+ SelfType::Opaque(o) => Type::Opaque(o.wrap_optional()),
+ SelfType::Struct(s) => Type::Struct(s),
+ SelfType::Enum(e) => Type::Enum(e),
+ }
+ }
+}
diff --git a/crates/diplomat_core/src/lib.rs b/crates/diplomat_core/src/lib.rs
new file mode 100644
index 0000000..8485372
--- /dev/null
+++ b/crates/diplomat_core/src/lib.rs
@@ -0,0 +1,15 @@
+//! The Diplomat core module contains common logic between
+//! the macro expansion and code generation. Right now, this
+//! is primarily the AST types that Diplomat generates while
+//! extracting APIs.
+
+#![allow(clippy::needless_lifetimes)] // we use named lifetimes for clarity
+#![warn(clippy::exhaustive_structs, clippy::exhaustive_enums)]
+
+pub mod ast;
+#[cfg(feature = "hir")]
+pub mod hir;
+
+mod environment;
+
+pub use environment::{Env, ModuleEnv};
diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index d274e01..e544806 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -182,6 +182,7 @@
"der_derive",
"derive_arbitrary",
"diplomat-runtime",
+ "diplomat_core",
"displaydoc",
"document-features",
"downcast",
@@ -1705,6 +1706,20 @@
checksum = "bc1708f176e12755d6d6571ad9b0ebbd3b428223b5cdf63a38eecf1479c13e70"
[[package]]
+name = "diplomat_core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58e5ba87fee6b8b9dcc575cfbc84ae97b8b9f891fa27f670996a4684e20bd178"
+dependencies = [
+ "proc-macro2 1.0.93",
+ "quote 1.0.38",
+ "serde",
+ "smallvec",
+ "strck",
+ "syn 2.0.96",
+]
+
+[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5075,6 +5090,9 @@
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
name = "strsim"
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index 0fea9bd..36ad7fb 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -91,6 +91,7 @@
der_derive = "=0.7.3"
derive_arbitrary = "=1.4.1"
diplomat-runtime = "=0.9.0"
+diplomat_core = "=0.9.0"
displaydoc = "=0.2.5"
document-features = "=0.2.10"
downcast = "=0.11.0"
diff --git a/pseudo_crate/crate-list.txt b/pseudo_crate/crate-list.txt
index 3594c8d..037d0c6 100644
--- a/pseudo_crate/crate-list.txt
+++ b/pseudo_crate/crate-list.txt
@@ -83,6 +83,7 @@
der_derive
derive_arbitrary
diplomat-runtime
+diplomat_core
displaydoc
document-features
downcast