Bug 1719535 - Part 6. mach vendor rust for ICU4X crates. r=platform-i18n-reviewers,supply-chain-reviewers,firefox-build-system-reviewers,glandium,dminor

Depends on D167675

Differential Revision: https://phabricator.services.mozilla.com/D167674
This commit is contained in:
Makoto Kato 2023-08-07 06:23:48 +00:00
parent d8a2fdf7da
commit 9fc41dc37c
1384 changed files with 151216 additions and 269 deletions

View File

@ -115,6 +115,11 @@ git = "https://github.com/mozilla/uniffi-rs.git"
rev = "c0e64b839018728d8153ce1758d391b7782e2e21"
replace-with = "vendored-sources"
[source."git+https://github.com/rust-diplomat/diplomat?rev=8d125999893fedfdf30595e97334c21ec4b18da9"]
git = "https://github.com/rust-diplomat/diplomat"
rev = "8d125999893fedfdf30595e97334c21ec4b18da9"
replace-with = "vendored-sources"
[source."git+https://github.com/rust-minidump/minidump-writer.git?rev=a15bd5cab6a3de251c0c23264be14b977c0af09c"]
git = "https://github.com/rust-minidump/minidump-writer.git"
rev = "a15bd5cab6a3de251c0c23264be14b977c0af09c"
@ -125,6 +130,11 @@ git = "https://github.com/rust-minidump/rust-minidump"
rev = "87a29fba5e19cfae5ebf73a57ba31504a3872545"
replace-with = "vendored-sources"
[source."git+https://github.com/unicode-org/icu4x?rev=14e9a3a9857be74582abe2dfa7ab799c5eaac873"]
git = "https://github.com/unicode-org/icu4x"
rev = "14e9a3a9857be74582abe2dfa7ab799c5eaac873"
replace-with = "vendored-sources"
# Take advantage of the fact that cargo will treat lines starting with #
# as comments to add preprocessing directives. This file can thus by copied

252
Cargo.lock generated
View File

@ -1323,6 +1323,36 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "diplomat"
version = "0.5.2"
source = "git+https://github.com/rust-diplomat/diplomat?rev=8d125999893fedfdf30595e97334c21ec4b18da9#8d125999893fedfdf30595e97334c21ec4b18da9"
dependencies = [
"diplomat_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diplomat-runtime"
version = "0.5.2"
source = "git+https://github.com/rust-diplomat/diplomat?rev=8d125999893fedfdf30595e97334c21ec4b18da9#8d125999893fedfdf30595e97334c21ec4b18da9"
[[package]]
name = "diplomat_core"
version = "0.5.2"
source = "git+https://github.com/rust-diplomat/diplomat?rev=8d125999893fedfdf30595e97334c21ec4b18da9#8d125999893fedfdf30595e97334c21ec4b18da9"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"serde",
"smallvec",
"strck_ident",
"syn",
]
[[package]]
name = "dirs"
version = "4.0.0"
@ -2561,6 +2591,116 @@ dependencies = [
"want",
]
[[package]]
name = "icu_capi"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c29e1e7407081a5e425b295ef28526ca4c3cd7ce4475e1769c2863bb18b9305"
dependencies = [
"diplomat",
"diplomat-runtime",
"icu_locid",
"icu_provider",
"icu_provider_adapters",
"icu_segmenter",
"icu_testdata",
"log",
"tinystr",
"writeable",
]
[[package]]
name = "icu_collections"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8302d8dfd6044d3ddb3f807a5ef3d7bbca9a574959c6d6e4dc39aa7012d0d5"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3003f85dccfc0e238ff567693248c59153a46f4e6125ba4020b973cef4d1d335"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_provider"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc312a7b6148f7dfe098047ae2494d12d4034f48ade58d4f353000db376e305"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_adapters"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ae1e2bd0c41728b77e7c46e9afdec5e2127d1eedacc684724667d50c126bd3"
dependencies = [
"icu_locid",
"icu_provider",
"tinystr",
"yoke",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b728b9421e93eff1d9f8681101b78fa745e0748c95c655c83f337044a7e10"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.107",
]
[[package]]
name = "icu_segmenter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3300a7b6bf187be98a57264ad094f11f2e062c2e8263132af010ff522ee5495"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid",
"icu_provider",
"num-traits",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_testdata"
version = "1.2.0"
dependencies = [
"icu_collections",
"icu_locid",
"icu_provider",
"icu_provider_adapters",
"icu_segmenter",
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.2.1"
@ -2817,6 +2957,7 @@ dependencies = [
"encoding_c",
"encoding_c_mem",
"gluesmith",
"icu_capi",
"mozglue-static",
"smoosh",
]
@ -2954,6 +3095,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "libm"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]]
name = "libsqlite3-sys"
version = "0.26.0"
@ -2998,6 +3145,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "litemap"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a04a5b2b6f54acba899926491d0a6c59d98012938ca2ab5befb281c034e8f94"
[[package]]
name = "lmdb-rkv"
version = "0.14.0"
@ -3827,6 +3980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -4985,6 +5139,22 @@ dependencies = [
"xpcom",
]
[[package]]
name = "strck"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be91090ded9d8f979d9fe921777342d37e769e0b6b7296843a7a38247240e917"
[[package]]
name = "strck_ident"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1c3802b169b3858a44667f221c9a0b3136e6019936ea926fc97fbad8af77202"
dependencies = [
"strck",
"unicode-ident",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -5288,6 +5458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
@ -5504,9 +5675,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.8"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
@ -5775,6 +5946,12 @@ dependencies = [
"serde",
]
[[package]]
name = "utf8_iter"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a8922555b9500e3d865caed19330172cd67cbf82203f1a3311d8c305cc9f33"
[[package]]
name = "uuid"
version = "1.3.0"
@ -6314,6 +6491,12 @@ dependencies = [
"euclid",
]
[[package]]
name = "writeable"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60e49e42bdb1d5dc76f4cd78102f8f0714d32edfa3efb82286eb0f0b1fc0da0f"
[[package]]
name = "xml-rs"
version = "0.8.4"
@ -6372,6 +6555,29 @@ dependencies = [
"linked-hash-map",
]
[[package]]
name = "yoke"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1848075a23a28f9773498ee9a0f2cf58fcbad4f8c0ccf84a210ab33c6ae495de"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.1"
source = "git+https://github.com/unicode-org/icu4x?rev=14e9a3a9857be74582abe2dfa7ab799c5eaac873#14e9a3a9857be74582abe2dfa7ab799c5eaac873"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeitstempel"
version = "0.1.1"
@ -6383,6 +6589,48 @@ dependencies = [
"once_cell",
]
[[package]]
name = "zerofrom"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df54d76c3251de27615dfcce21e636c172dafb2549cd7fd93e21c66f6ca6bea2"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.2"
source = "git+https://github.com/unicode-org/icu4x?rev=14e9a3a9857be74582abe2dfa7ab799c5eaac873#14e9a3a9857be74582abe2dfa7ab799c5eaac873"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerovec"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198f54134cd865f437820aa3b43d0ad518af4e68ee161b444cdd15d8e567c8ea"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.9.4"
source = "git+https://github.com/unicode-org/icu4x?rev=14e9a3a9857be74582abe2dfa7ab799c5eaac873#14e9a3a9857be74582abe2dfa7ab799c5eaac873"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zip"
version = "0.6.4"

View File

@ -207,6 +207,13 @@ tabs = { git = "https://github.com/mozilla/application-services", rev = "25972c3
viaduct = { git = "https://github.com/mozilla/application-services", rev = "25972c388a4cf3a6d8256504f3a09b711db2fc6a" }
webext-storage = { git = "https://github.com/mozilla/application-services", rev = "25972c388a4cf3a6d8256504f3a09b711db2fc6a" }
# ICU4X 1.2 with synstructure 0.13.x / syn 2.x. When updating to next version, this should be removed.
diplomat = { git = "https://github.com/rust-diplomat/diplomat", rev = "8d125999893fedfdf30595e97334c21ec4b18da9" }
diplomat-runtime = { git = "https://github.com/rust-diplomat/diplomat", rev = "8d125999893fedfdf30595e97334c21ec4b18da9" }
yoke-derive = { git = "https://github.com/unicode-org/icu4x", rev = "14e9a3a9857be74582abe2dfa7ab799c5eaac873" }
zerofrom-derive = { git = "https://github.com/unicode-org/icu4x", rev = "14e9a3a9857be74582abe2dfa7ab799c5eaac873" }
zerovec-derive = { git = "https://github.com/unicode-org/icu4x", rev = "14e9a3a9857be74582abe2dfa7ab799c5eaac873" }
# Patch mio 0.6 to use winapi 0.3 and miow 0.3, getting rid of winapi 0.2.
# There is not going to be new version of mio 0.6, mio now being >= 0.7.11.
[patch.crates-io.mio]

View File

@ -397,6 +397,14 @@ start = "2022-12-16"
end = "2024-06-21"
notes = "Maintained by the Glean and Application Services teams"
[[wildcard-audits.utf8_iter]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
user-id = 4484 # Henri Sivonen (hsivonen)
start = "2022-04-19"
end = "2024-06-16"
notes = "Maintained by Henri Sivonen who works at Mozilla."
[[wildcard-audits.webdriver]]
who = "Henrik Skupin <mail@hskupin.info>"
criteria = "safe-to-deploy"
@ -1185,6 +1193,39 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.10.3 -> 0.10.6"
[[audits.diplomat]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.5.2"
notes = "This crate is FFI wrapper generator using by ICU4X ffi libraries. This uses unsafe code to convert paramenters, I have reviewed this and generated headers."
[[audits.diplomat]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
delta = "0.5.2 -> 0.5.2@git:8d125999893fedfdf30595e97334c21ec4b18da9"
[[audits.diplomat-runtime]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.5.2"
notes = "This crate is FFI wrapper generator runtime using by ICU4X ffi libraries. This uses unsafe code for memory access of FFI. I have reviewed carefully."
[[audits.diplomat-runtime]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
delta = "0.5.2 -> 0.5.2@git:8d125999893fedfdf30595e97334c21ec4b18da9"
[[audits.diplomat_core]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.5.2"
notes = "This crate contains unsafe code, no network and no file access."
[[audits.diplomat_core]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
delta = "0.5.2 -> 0.5.2@git:8d125999893fedfdf30595e97334c21ec4b18da9"
[[audits.displaydoc]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
@ -1800,6 +1841,54 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-run"
delta = "0.14.23 -> 0.14.24"
[[audits.icu_capi]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.2"
notes = "This crate is C/C++ FFI for ICU4X using diplomat crate. no unsafe and no file access etc on this crate."
[[audits.icu_collections]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.0"
notes = "This crate is used by ICU4X for internal data structure. There is no fileaccess and network access. This uses unsafe block, but we confirm data is valid before."
[[audits.icu_locid]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.0"
notes = "This has unsafe block to handle ascii string in utf-8 string. I've vetted the one instance of unsafe code."
[[audits.icu_provider]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.0"
notes = "Although this has unsafe block, this has a commnet why this is safety and I audited code. Also, this doesn't have file access and network access."
[[audits.icu_provider_adapters]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.0"
notes = "This is one of ICU4X data provider crates that depends on data type. This has no unsafe code and uses no ambient capabilities."
[[audits.icu_provider_macros]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.0"
notes = "This crate is macros for ICU4X's data provider implementer. This has no unsafe code and uses no ambient capabilities."
[[audits.icu_segmenter]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.1"
notes = "Original authors are Makoto Kato and Ting-Yu Lin who work at Mozilla. This crate uses unsafe to matrix calculation, but it is safety to check length. And there is no filesystem / network access."
[[audits.icu_testdata]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "1.2.0"
notes = "This is just ICU4X data only that is generated by ICU4X datagen. Generated data is in unsafe block to use zero-copy implmentation, but it is safety."
[[audits.idna]]
who = "Bobby Holley <bobbyholley@gmail.com>"
criteria = "safe-to-deploy"
@ -1896,6 +1985,12 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.7.3 -> 0.7.4"
[[audits.libm]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.2.6"
notes = "This crate uses unsafe block, but this doesn't have network and file access. I audited code."
[[audits.libsqlite3-sys]]
who = "Ben Dean-Kawamura <bdk@mozilla.com>"
criteria = "safe-to-deploy"
@ -1912,6 +2007,12 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-run"
delta = "0.5.4 -> 0.5.6"
[[audits.litemap]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.7.0"
notes = "This crete has no unsafe code, no file acceess and no network access."
[[audits.lmdb-rkv]]
who = "Bobby Holley <bobbyholley@gmail.com>"
criteria = "safe-to-deploy"
@ -3019,6 +3120,18 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.4.4 -> 0.4.7"
[[audits.strck]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.1.2"
notes = "This crate uses unsafe lock to keep invariant. I auditted code. Also, this doesn't have file access and network access."
[[audits.strck_ident]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.1.2"
notes = "This crate doesn't use unsafe block, network access and filesystem access."
[[audits.subtle]]
who = "Simon Friedberger <simon@mozilla.com>"
criteria = "safe-to-deploy"
@ -3318,6 +3431,11 @@ who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
delta = "0.9.0 -> 0.9.1"
[[audits.unicode-bidi]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
delta = "0.3.8 -> 0.3.13"
[[audits.unicode-ident]]
who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-deploy"
@ -4013,12 +4131,54 @@ criteria = "safe-to-deploy"
version = "0.1.0"
notes = "Written and maintained by Gfx team at Mozilla."
[[audits.writeable]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.5.2"
notes = "writeable is a variation of fmt::Write with sink version. This uses `unsafe` block to handle potentially-invalid UTF-8 character. I've vetted the one instance of unsafe code."
[[audits.xmldecl]]
who = "Henri Sivonen <hsivonen@hsivonen.fi>"
criteria = "safe-to-deploy"
version = "0.2.0"
notes = "I, Henri Sivonen, wrote this crate myself for Gecko even though it's published on crates.io."
[[audits.yoke]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.7.1"
notes = "This crate is for zero-copy serialization for ICU4X data structure, and maintained by ICU4X team. Since this uses unsafe block for serialization, I audited code."
[[audits.yoke-derive]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.7.1@git:14e9a3a9857be74582abe2dfa7ab799c5eaac873"
notes = "This crate is a helper for yoke crate that is ICU4X data structure, and maintained by ICU4X team. Since this uses unsafe block for serialization, all has the comment why this uses unsafe and I audited code."
[[audits.zerofrom]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.1.2"
notes = "This crate is zero-copy version of \"From\". This has no unsafe code and uses no ambient capabilities."
[[audits.zerofrom-derive]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.1.2@git:14e9a3a9857be74582abe2dfa7ab799c5eaac873"
notes = "This is custom derives for `ZeroFrom` that is from zerofrom crate. This has no unsafe code and uses no ambient capabilities."
[[audits.zerovec]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.9.4"
notes = "This crate is zero-copy data structure implmentation. Although this uses unsafe block in several code, it requires for zero-copy. And this has a comment in code why this uses unsafe and I audited code."
[[audits.zerovec-derive]]
who = "Makoto Kato <m_kato@ga2.so-net.ne.jp>"
criteria = "safe-to-deploy"
version = "0.9.4@git:14e9a3a9857be74582abe2dfa7ab799c5eaac873"
notes = "This is custom derives for `ZeroVec` that is from zerovec crate. Although this uses unsafe block for zero-copy, this has a comment in code why this uses unsafe and I audited code."
[[audits.zip]]
who = "Mike Hommey <mh+mozilla@glandium.org>"
criteria = "safe-to-run"

View File

@ -39,6 +39,18 @@ notes = "This is a pinned version of the upstream code, presumably to get a fix
audit-as-crates-io = true
notes = "This is upstream plus a warning fix from bug 1823866."
[policy.diplomat]
audit-as-crates-io = true
notes = "Upstream version not to use syn 1.x"
[policy.diplomat-runtime]
audit-as-crates-io = true
notes = "Upstream version not to use syn 1.x"
[policy.diplomat_core]
audit-as-crates-io = true
notes = "Upstream version not to use syn 1.x"
[policy.firefox-on-glean]
audit-as-crates-io = false
notes = "The crates.io version of this is just a placeholder to allow public crates to depend on firefox-on-glean."
@ -64,6 +76,10 @@ notes = "Used for fuzzing."
criteria = "safe-to-run"
notes = "Used for testing."
[policy.icu_testdata]
audit-as-crates-io = false
notes = "Customized ICU4X baked data only that Gecko wants"
[policy.l10nregistry]
dependency-criteria = { fluent-testing = "safe-to-run", tokio = "safe-to-run" }
notes = "This crate has two testing-only dependencies which are specified as regular-but-optional rather than a dev-dependencies, because they need to be available to both benchmarks and integration tests."
@ -221,6 +237,18 @@ notes = "Upstream project which we pin."
[policy.wr_malloc_size_of]
audit-as-crates-io = false
[policy.yoke-derive]
audit-as-crates-io = true
notes = "Upstream version not to use syn 1.x"
[policy.zerofrom-derive]
audit-as-crates-io = true
notes = "Upstream version not to use syn 1.x"
[policy.zerovec-derive]
audit-as-crates-io = true
notes = "Upstream version not to use syn 1.x"
[[exemptions.ahash]]
version = "0.7.6"
criteria = "safe-to-deploy"

View File

@ -574,6 +574,13 @@ user-id = 48
user-login = "badboy"
user-name = "Jan-Erik Rediger"
[[publisher.utf8_iter]]
version = "1.0.3"
when = "2022-09-09"
user-id = 4484
user-login = "hsivonen"
user-name = "Henri Sivonen"
[[publisher.walkdir]]
version = "2.3.2"
when = "2021-03-22"

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"7fc35f8643067872335fe85cf4a6a4289c1b82156cf1216d223b44513b8d0d98","LICENSE-APACHE":"639c20c7f14fb122750d5ad1a6cfb116d9bf8d103e709ee40949e5a12a731666","LICENSE-MIT":"3337fe6e4a3830ad87c23cb9d6d750f9a1e5c45efc08de9c76c1a207fc6966c4","src/lib.rs":"6c019d412bb5534abd3869a16574599dcff730b89a576c3cf4c5590a1d449d17","src/result.rs":"2b07ce3cbb02a141abc35742f307cc4ef2b097ce610e04d92a50c469f354401d","src/wasm_glue.rs":"f26aa119aa291a3cfafc16b73f713a391718294a51e2c656d0093cf1fd2befac","src/writeable.rs":"80d8a93feba545fcb3150b8daf546f3e060b0c8c11f13917409dd455abe0c3bd"},"package":null}

View File

@ -0,0 +1,34 @@
# 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 = "2018"
name = "diplomat-runtime"
version = "0.5.2"
authors = [
"Shadaj Laddad <shadaj@users.noreply.github.com>",
"Manish Goregaokar <manishsmail@gmail.com>",
"Quinn Okabayashi <QnnOkabayashi@users.noreply.github.com>",
]
description = "Common runtime utilities used by diplomat codegen"
documentation = "https://docs.rs/diplomat_core/"
keywords = [
"ffi",
"codegen",
]
categories = ["development-tools"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rust-diplomat/diplomat"
[lib]
path = "src/lib.rs"
[dependencies]

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,33 @@
#![cfg_attr(not(any(target_arch = "wasm32")), no_std)]
extern crate alloc;
use alloc::alloc::Layout;
#[cfg(target_arch = "wasm32")]
mod wasm_glue;
#[cfg(target_arch = "wasm32")]
pub use wasm_glue::{console_log, console_trace, console_warn};
mod writeable;
pub use writeable::DiplomatWriteable;
mod result;
pub use result::DiplomatResult;
/// Allocates a buffer of a given size in Rust's memory.
///
/// # Safety
/// - The allocated buffer must be freed with [`diplomat_free()`].
#[no_mangle]
pub unsafe extern "C" fn diplomat_alloc(size: usize, align: usize) -> *mut u8 {
alloc::alloc::alloc(Layout::from_size_align(size, align).unwrap())
}
/// Frees a buffer that was allocated in Rust's memory.
/// # Safety
/// - `ptr` must be a pointer to a valid buffer allocated by [`diplomat_alloc()`].
#[no_mangle]
pub unsafe extern "C" fn diplomat_free(ptr: *mut u8, size: usize, align: usize) {
alloc::alloc::dealloc(ptr, Layout::from_size_align(size, align).unwrap())
}

View File

@ -0,0 +1,60 @@
use core::mem::ManuallyDrop;
#[repr(C)]
union DiplomatResultValue<T, E> {
ok: ManuallyDrop<T>,
err: ManuallyDrop<E>,
}
/// A `Result`-like type that can be passed across the FFI boundary
/// as a value. Use this type when returning a result from an FFI
/// function instead of `Result`.
#[repr(C)]
pub struct DiplomatResult<T, E> {
value: DiplomatResultValue<T, E>,
pub is_ok: bool,
}
impl<T, E> Drop for DiplomatResult<T, E> {
fn drop(&mut self) {
unsafe {
if self.is_ok {
let _ = ManuallyDrop::take(&mut self.value.ok);
} else {
let _ = ManuallyDrop::take(&mut self.value.err);
}
}
}
}
impl<T, E> From<Result<T, E>> for DiplomatResult<T, E> {
fn from(result: Result<T, E>) -> Self {
match result {
Result::Ok(ok) => DiplomatResult {
value: DiplomatResultValue {
ok: ManuallyDrop::new(ok),
},
is_ok: true,
},
Result::Err(err) => DiplomatResult {
value: DiplomatResultValue {
err: ManuallyDrop::new(err),
},
is_ok: false,
},
}
}
}
impl<T, E> From<DiplomatResult<T, E>> for Result<T, E> {
fn from(mut result: DiplomatResult<T, E>) -> Result<T, E> {
unsafe {
if result.is_ok {
Ok(ManuallyDrop::take(&mut result.value.ok))
} else {
Err(ManuallyDrop::take(&mut result.value.err))
}
}
}
}

View File

@ -0,0 +1,43 @@
// minimal WASM logger based on https://github.com/DeMille/wasm-glue
extern "C" {
fn trace_js(ptr: *const u8, len: usize);
fn warn_js(ptr: *const u8, len: usize);
fn log_js(ptr: *const u8, len: usize);
}
/// Throw an exception.
pub fn console_trace(msg: &str) {
unsafe {
trace_js(msg.as_ptr(), msg.len());
}
}
/// Write a message to `console.warn`.
pub fn console_warn(msg: &str) {
unsafe { warn_js(msg.as_ptr(), msg.len()) }
}
/// Write a message to `console.log`.
pub fn console_log(msg: &str) {
unsafe { log_js(msg.as_ptr(), msg.len()) }
}
#[no_mangle]
pub unsafe extern "C" fn diplomat_init() {
#[cfg(debug_assertions)]
// Sets a custom panic hook using `trace_js`, which by default crates a JS error
std::panic::set_hook(Box::new(|info| {
let file = info.location().unwrap().file();
let line = info.location().unwrap().line();
let col = info.location().unwrap().column();
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(&s) => s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => s.as_str(),
None => "Box<Any>",
},
};
console_trace(&format!("Panicked at '{msg}', {file}:{line}:{col}"));
}));
}

View File

@ -0,0 +1,189 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::{fmt, ptr};
/// An object that can one can write UTF-8 strings to
///
/// This allows the C API to write to arbitrary kinds of objects, for example a
/// C++ std::string or a char buffer.
///
/// The way to use this object is to fill out the `buf`, `len`, `cap` fields with
/// appropriate values for the buffer, its current length, and its current capacity,
/// and `flush` and `grow` with appropriate callbacks (using `context` to reference any
/// state they need). This object will be passed by mutable reference to the Rust side,
/// and Rust will write to it, calling `grow()` as necessary. Once done, it will call `flush()`
/// to update any state on `context` (e.g. adding a null terminator, updating the length).
/// The object on the foreign side will be directly usable after this, the foreign side
/// need not perform additional state updates after passing an [`DiplomatWriteable`] to
/// a function.
///
/// [`diplomat_simple_writeable()`] can be used to write to a fixed-size char buffer.
///
/// May be extended in the future to support further invariants
///
/// DiplomatWriteable will not perform any cleanup on `context` or `buf`, these are logically
/// "borrows" from the FFI side.
///
/// # Safety invariants:
/// - `flush()` and `grow()` will be passed `self` including `context` and it should always be safe to do so.
/// `context` may be null, however `flush()` and `grow()` must then be ready to receive it as such.
/// - `buf` must be `cap` bytes long
/// - `grow()` must either return false or update `buf` and `cap` for a valid buffer
/// of at least the requested buffer size
/// - `DiplomatWriteable::flush()` will be automatically called by Diplomat. `flush()` might also be called
/// (erroneously) on the Rust side (it's a public method), so it must be idempotent.
#[repr(C)]
pub struct DiplomatWriteable {
/// Context pointer for additional data needed by `grow()` and `flush()`. May be `null`.
///
/// The pointer may reference structured data on the foreign side,
/// such as C++ std::string, used to reallocate buf.
context: *mut c_void,
/// The raw string buffer, which will be mutated on the Rust side.
buf: *mut u8,
/// The current filled size of the buffer
len: usize,
/// The current capacity of the buffer
cap: usize,
/// Called by Rust to indicate that there is no more data to write.
///
/// May be called multiple times.
///
/// Arguments:
/// - `self` (`*mut DiplomatWriteable`): This `DiplomatWriteable`
flush: extern "C" fn(*mut DiplomatWriteable),
/// Called by Rust to request more capacity in the buffer. The implementation should allocate a new
/// buffer and copy the contents of the old buffer into the new buffer, updating `self.buf` and `self.cap`
///
/// Arguments:
/// - `self` (`*mut DiplomatWriteable`): This `DiplomatWriteable`
/// - `capacity` (`usize`): The requested capacity.
///
/// Returns: `true` if the allocation succeeded. Should not update any state if it failed.
grow: extern "C" fn(*mut DiplomatWriteable, usize) -> bool,
}
impl DiplomatWriteable {
/// Call this function before releasing the buffer to C
pub fn flush(&mut self) {
(self.flush)(self);
}
}
impl fmt::Write for DiplomatWriteable {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let needed_len = self.len + s.len();
if needed_len > self.cap {
let success = (self.grow)(self, needed_len);
if !success {
return Err(fmt::Error);
}
}
debug_assert!(needed_len <= self.cap);
unsafe {
ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf.add(self.len), s.len());
}
self.len = needed_len;
Ok(())
}
}
/// Create an `DiplomatWriteable` that can write to a fixed-length stack allocated `u8` buffer.
///
/// Once done, this will append a null terminator to the written string.
///
/// # Safety
///
/// - `buf` must be a valid pointer to a region of memory that can hold at `buf_size` bytes
#[no_mangle]
pub unsafe extern "C" fn diplomat_simple_writeable(
buf: *mut u8,
buf_size: usize,
) -> DiplomatWriteable {
extern "C" fn grow(_this: *mut DiplomatWriteable, _cap: usize) -> bool {
false
}
extern "C" fn flush(this: *mut DiplomatWriteable) {
unsafe {
debug_assert!((*this).len <= (*this).cap);
let buf = (*this).buf;
ptr::write(buf.add((*this).len), 0)
}
}
DiplomatWriteable {
context: ptr::null_mut(),
buf,
len: 0,
// keep an extra byte in our pocket for the null terminator
cap: buf_size - 1,
flush,
grow,
}
}
/// Create an [`DiplomatWriteable`] that can write to a dynamically allocated buffer managed by Rust.
///
/// Use [`diplomat_buffer_writeable_destroy()`] to free the writable and its underlying buffer.
#[no_mangle]
pub extern "C" fn diplomat_buffer_writeable_create(cap: usize) -> *mut DiplomatWriteable {
extern "C" fn grow(this: *mut DiplomatWriteable, new_cap: usize) -> bool {
unsafe {
let this = this.as_mut().unwrap();
let mut vec = Vec::from_raw_parts(this.buf, 0, this.cap);
vec.reserve(new_cap);
this.cap = vec.capacity();
this.buf = vec.as_mut_ptr();
core::mem::forget(vec);
}
true
}
extern "C" fn flush(_: *mut DiplomatWriteable) {}
let mut vec = Vec::<u8>::with_capacity(cap);
let ret = DiplomatWriteable {
context: ptr::null_mut(),
buf: vec.as_mut_ptr(),
len: 0,
cap,
flush,
grow,
};
core::mem::forget(vec);
Box::into_raw(Box::new(ret))
}
/// Grabs a pointer to the underlying buffer of a writable.
///
/// # Safety
/// - The returned pointer is valid until the passed writable is destroyed.
/// - `this` must be a pointer to a valid [`DiplomatWriteable`] constructed by
/// [`diplomat_buffer_writeable_create()`].
#[no_mangle]
pub extern "C" fn diplomat_buffer_writeable_get_bytes(this: &DiplomatWriteable) -> *mut u8 {
this.buf
}
/// Gets the length in bytes of the content written to the writable.
///
/// # Safety
/// - `this` must be a pointer to a valid [`DiplomatWriteable`] constructed by
/// [`diplomat_buffer_writeable_create()`].
#[no_mangle]
pub extern "C" fn diplomat_buffer_writeable_len(this: &DiplomatWriteable) -> usize {
this.len
}
/// Destructor for Rust-memory backed writables.
///
/// # Safety
/// - `this` must be a pointer to a valid [`DiplomatWriteable`] constructed by
/// [`diplomat_buffer_writeable_create()`].
#[no_mangle]
pub unsafe extern "C" fn diplomat_buffer_writeable_destroy(this: *mut DiplomatWriteable) {
let this = Box::from_raw(this);
let vec = Vec::from_raw_parts(this.buf, 0, this.cap);
drop(vec);
drop(this);
}

View File

@ -0,0 +1 @@
{"files":{"Cargo.toml":"8ae49bf1abfabd0c4013a0629f806795773baeca9e99605e485c6d9060d5f18e","LICENSE-APACHE":"639c20c7f14fb122750d5ad1a6cfb116d9bf8d103e709ee40949e5a12a731666","LICENSE-MIT":"3337fe6e4a3830ad87c23cb9d6d750f9a1e5c45efc08de9c76c1a207fc6966c4","src/enum_convert.rs":"c0068cb8b563043030186cd9a0be6a4eac1a5f1761fe3646a99528e6d3dc5f54","src/lib.rs":"e606ade5f7cc5b43f56b07511b34fe5c0eb2e53640a347d17585062bf9cdea7c","src/snapshots/diplomat__tests__cfgd_struct.snap":"2874497e83ba3f43541d90869bfa428973c5b23df7fec2826e187d530f1a620b","src/snapshots/diplomat__tests__cfged_method-2.snap":"45f4c58a153cfc8a01d24fcd4474cd7ff84e34ed8b75e718143c16edd884c510","src/snapshots/diplomat__tests__cfged_method.snap":"b478d2d14e01209b45032b092bf91a2068f08f704b2395bc3ebd2ada21141077","src/snapshots/diplomat__tests__method_taking_mutable_slice.snap":"eafa335a999e416a044d32106e096ef57b7f41fb2f74b03d4adce13e7179b03d","src/snapshots/diplomat__tests__method_taking_mutable_str.snap":"e4c65337861a78b3c9762545fcdbccc1169bab9183ff750fc467a5367dba9c56","src/snapshots/diplomat__tests__method_taking_slice.snap":"eb2d7d00381ddef71411c170ce27d2490acfe9322bc3de397fe1cedcedeeee7b","src/snapshots/diplomat__tests__method_taking_str.snap":"92aa38d8618f0d52d5bc967a8d67a87f4b9cdc4f8651c624a9cc7b73033dbaa4","src/snapshots/diplomat__tests__mod_with_enum.snap":"fc225e910aa1afe496eb8d4da4342894f7786c53e12725b2f70018cf5230dddc","src/snapshots/diplomat__tests__mod_with_rust_result.snap":"48e30564d2cf0477db7062c58842b264d0cfec1d635e7dbaf12c12a2e9f1ab31","src/snapshots/diplomat__tests__mod_with_writeable_result.snap":"6ddbc34dbf7d362366cddda70c87a2f25d7ba0821ccbab90b5a79748f1064593","src/snapshots/diplomat__tests__multilevel_borrows.snap":"708fb54c2c3b8498aac83c20af891e12212874b94691fd734fd57f500ce54666","src/snapshots/diplomat__tests__self_params.snap":"1f6652799973e4afa751afebd6306e71ea439640cc1cae9848c5757d060bf699","src/transparent_convert.rs":"dde901986a6709a21f359596e85bc4fd009bb645c79b698d5af8e2a603996ac4"},"package":null}

52
third_party/rust/diplomat/Cargo.toml vendored Normal file
View File

@ -0,0 +1,52 @@
# 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 = "2018"
name = "diplomat"
version = "0.5.2"
authors = [
"Shadaj Laddad <shadaj@users.noreply.github.com>",
"Manish Goregaokar <manishsmail@gmail.com>",
"Quinn Okabayashi <QnnOkabayashi@users.noreply.github.com>",
]
description = "The diplomat FFI generation macro"
documentation = "https://docs.rs/diplomat_core/"
keywords = [
"ffi",
"codegen",
]
categories = ["development-tools"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rust-diplomat/diplomat"
[lib]
path = "src/lib.rs"
proc-macro = true
[dependencies]
proc-macro2 = "1.0.27"
quote = "1.0"
[dependencies.diplomat_core]
version = "0.5.2"
path = "../core"
[dependencies.syn]
version = "2.0"
features = [
"full",
"extra-traits",
]
[dev-dependencies]
insta = "1.7.1"
tempfile = "3.2.0"

201
third_party/rust/diplomat/LICENSE-APACHE vendored Normal file
View File

@ -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.

27
third_party/rust/diplomat/LICENSE-MIT vendored Normal file
View File

@ -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.

View File

@ -0,0 +1,85 @@
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::*;
// An attribute that is a list of idents
pub struct EnumConvertAttribute {
path: Path,
needs_wildcard: bool,
}
impl Parse for EnumConvertAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let paths = input.parse_terminated(Path::parse, Token![,])?;
if paths.is_empty() {
return Err(input.error("#[diplomat::enum_convert] needs a path argument"));
}
let needs_wildcard = if paths.len() == 2 {
if let Some(ident) = paths[1].get_ident() {
if ident == "needs_wildcard" {
true
} else {
return Err(input.error(
"#[diplomat::enum_convert] only recognizes needs_wildcard keyword",
));
}
} else {
return Err(
input.error("#[diplomat::enum_convert] only recognizes needs_wildcard keyword")
);
}
} else if paths.len() > 1 {
return Err(input.error("#[diplomat::enum_convert] only supports up to two arguments"));
} else {
// no needs_wildcard marker
false
};
Ok(EnumConvertAttribute {
path: paths[0].clone(),
needs_wildcard,
})
}
}
pub fn gen_enum_convert(attr: EnumConvertAttribute, input: ItemEnum) -> proc_macro2::TokenStream {
let mut from_arms = vec![];
let mut into_arms = vec![];
let this_name = &input.ident;
let other_name = &attr.path;
for variant in &input.variants {
if variant.fields != Fields::Unit {
return Error::new(variant.ident.span(), "variant may not have fields")
.to_compile_error();
}
let variant_name = &variant.ident;
from_arms.push(quote!(#other_name::#variant_name => Self::#variant_name));
into_arms.push(quote!(#this_name::#variant_name => Self::#variant_name));
}
if attr.needs_wildcard {
let error = format!(
"Encountered unknown field for {}",
other_name.to_token_stream()
);
from_arms.push(quote!(_ => unreachable!(#error)))
}
quote! {
impl From<#other_name> for #this_name {
fn from(other: #other_name) -> Self {
match other {
#(#from_arms,)*
}
}
}
impl From<#this_name> for #other_name {
fn from(this: #this_name) -> Self {
match this {
#(#into_arms,)*
}
}
}
}
}

686
third_party/rust/diplomat/src/lib.rs vendored Normal file
View File

@ -0,0 +1,686 @@
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::*;
use diplomat_core::ast;
mod enum_convert;
mod transparent_convert;
fn cfgs_to_stream(attrs: &[String]) -> proc_macro2::TokenStream {
attrs.iter().fold(quote!(), |prev, attr| {
let attr = attr.parse::<proc_macro2::TokenStream>().unwrap();
quote!(#prev #attr)
})
}
fn gen_params_at_boundary(param: &ast::Param, expanded_params: &mut Vec<FnArg>) {
match &param.ty {
ast::TypeName::StrReference(_) | ast::TypeName::PrimitiveSlice(..) => {
let data_type = if let ast::TypeName::PrimitiveSlice(.., prim) = &param.ty {
ast::TypeName::Primitive(*prim).to_syn().to_token_stream()
} else {
quote! { u8 }
};
expanded_params.push(FnArg::Typed(PatType {
attrs: vec![],
pat: Box::new(Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new(&format!("{}_diplomat_data", param.name), Span::call_site()),
subpat: None,
})),
colon_token: syn::token::Colon(Span::call_site()),
ty: Box::new(
parse2({
if let ast::TypeName::PrimitiveSlice(_, ast::Mutability::Mutable, _) =
&param.ty
{
quote! { *mut #data_type }
} else {
quote! { *const #data_type }
}
})
.unwrap(),
),
}));
expanded_params.push(FnArg::Typed(PatType {
attrs: vec![],
pat: Box::new(Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new(&format!("{}_diplomat_len", param.name), Span::call_site()),
subpat: None,
})),
colon_token: syn::token::Colon(Span::call_site()),
ty: Box::new(
parse2(quote! {
usize
})
.unwrap(),
),
}));
}
o => {
expanded_params.push(FnArg::Typed(PatType {
attrs: vec![],
pat: Box::new(Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new(param.name.as_str(), Span::call_site()),
subpat: None,
})),
colon_token: syn::token::Colon(Span::call_site()),
ty: Box::new(o.to_syn()),
}));
}
}
}
fn gen_params_invocation(param: &ast::Param, expanded_params: &mut Vec<Expr>) {
match &param.ty {
ast::TypeName::StrReference(_) | ast::TypeName::PrimitiveSlice(..) => {
let data_ident =
Ident::new(&format!("{}_diplomat_data", param.name), Span::call_site());
let len_ident = Ident::new(&format!("{}_diplomat_len", param.name), Span::call_site());
let tokens = if let ast::TypeName::PrimitiveSlice(_, mutability, _) = &param.ty {
match mutability {
ast::Mutability::Mutable => quote! {
unsafe { core::slice::from_raw_parts_mut(#data_ident, #len_ident) }
},
ast::Mutability::Immutable => quote! {
unsafe { core::slice::from_raw_parts(#data_ident, #len_ident) }
},
}
} else {
// TODO(#57): don't just unwrap? or should we assume that the other side gives us a good value?
quote! {
unsafe {
core::str::from_utf8(core::slice::from_raw_parts(#data_ident, #len_ident)).unwrap()
}
}
};
expanded_params.push(parse2(tokens).unwrap());
}
ast::TypeName::Result(_, _, _) => {
let param = &param.name;
expanded_params.push(parse2(quote!(#param.into())).unwrap());
}
_ => {
expanded_params.push(Expr::Path(ExprPath {
attrs: vec![],
qself: None,
path: Ident::new(param.name.as_str(), Span::call_site()).into(),
}));
}
}
}
fn gen_custom_type_method(strct: &ast::CustomType, m: &ast::Method) -> Item {
let self_ident = Ident::new(strct.name().as_str(), Span::call_site());
let method_ident = Ident::new(m.name.as_str(), Span::call_site());
let extern_ident = Ident::new(m.full_path_name.as_str(), Span::call_site());
let mut all_params = vec![];
m.params.iter().for_each(|p| {
gen_params_at_boundary(p, &mut all_params);
});
let mut all_params_invocation = vec![];
m.params.iter().for_each(|p| {
gen_params_invocation(p, &mut all_params_invocation);
});
let this_ident = Pat::Ident(PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: Ident::new("this", Span::call_site()),
subpat: None,
});
if let Some(self_param) = &m.self_param {
all_params.insert(
0,
FnArg::Typed(PatType {
attrs: vec![],
pat: Box::new(this_ident.clone()),
colon_token: syn::token::Colon(Span::call_site()),
ty: Box::new(self_param.to_typename().to_syn()),
}),
);
}
let lifetimes = {
let lifetime_env = &m.lifetime_env;
if lifetime_env.is_empty() {
quote! {}
} else {
quote! { <#lifetime_env> }
}
};
let method_invocation = if m.self_param.is_some() {
quote! { #this_ident.#method_ident }
} else {
quote! { #self_ident::#method_ident }
};
let (return_tokens, maybe_into) = if let Some(return_type) = &m.return_type {
if let ast::TypeName::Result(ok, err, true) = return_type {
let ok = ok.to_syn();
let err = err.to_syn();
(
quote! { -> diplomat_runtime::DiplomatResult<#ok, #err> },
quote! { .into() },
)
} else {
let return_type_syn = return_type.to_syn();
(quote! { -> #return_type_syn }, quote! {})
}
} else {
(quote! {}, quote! {})
};
let writeable_flushes = m
.params
.iter()
.filter(|p| p.is_writeable())
.map(|p| {
let p = &p.name;
quote! { #p.flush(); }
})
.collect::<Vec<_>>();
let cfg = cfgs_to_stream(&m.cfg_attrs);
if writeable_flushes.is_empty() {
Item::Fn(syn::parse_quote! {
#[no_mangle]
#cfg
extern "C" fn #extern_ident#lifetimes(#(#all_params),*) #return_tokens {
#method_invocation(#(#all_params_invocation),*) #maybe_into
}
})
} else {
Item::Fn(syn::parse_quote! {
#[no_mangle]
#cfg
extern "C" fn #extern_ident#lifetimes(#(#all_params),*) #return_tokens {
let ret = #method_invocation(#(#all_params_invocation),*);
#(#writeable_flushes)*
ret #maybe_into
}
})
}
}
struct AttributeInfo {
repr: bool,
opaque: bool,
}
impl AttributeInfo {
fn extract(attrs: &mut Vec<Attribute>) -> Self {
let mut repr = false;
let mut opaque = false;
attrs.retain(|attr| {
let ident = &attr.path().segments.iter().next().unwrap().ident;
if ident == "repr" {
repr = true;
// don't actually extract repr attrs, just detect them
return true;
} else if ident == "diplomat" {
if attr.path().segments.len() == 2 {
let seg = &attr.path().segments.iter().nth(1).unwrap().ident;
if seg == "opaque" {
opaque = true;
return false;
} else if seg == "rust_link" || seg == "out" {
// diplomat-tool reads these, not diplomat::bridge.
// throw them away so rustc doesn't complain about unknown attributes
return false;
} else if seg == "enum_convert" || seg == "transparent_convert" {
// diplomat::bridge doesn't read this, but it's handled separately
// as an attribute
return true;
} else {
panic!("Only #[diplomat::opaque] and #[diplomat::rust_link] are supported")
}
} else {
panic!("#[diplomat::foo] attrs have a single-segment path name")
}
}
true
});
Self { repr, opaque }
}
}
fn gen_bridge(input: ItemMod) -> ItemMod {
let module = ast::Module::from_syn(&input, true);
let (brace, mut new_contents) = input.content.unwrap();
new_contents.iter_mut().for_each(|c| match c {
Item::Struct(s) => {
let info = AttributeInfo::extract(&mut s.attrs);
if info.opaque || !info.repr {
let repr = if info.opaque {
// Normal opaque types don't need repr(transparent) because the inner type is
// never referenced. #[diplomat::transparent_convert] handles adding repr(transparent)
// on its own
quote!()
} else {
quote!(#[repr(C)])
};
*s = syn::parse_quote! {
#repr
#s
}
}
}
Item::Enum(e) => {
let info = AttributeInfo::extract(&mut e.attrs);
if info.opaque {
panic!("#[diplomat::opaque] not allowed on enums")
}
*e = syn::parse_quote! {
#[repr(C)]
#e
};
}
Item::Impl(i) => {
for item in &mut i.items {
if let syn::ImplItem::Fn(ref mut m) = *item {
let info = AttributeInfo::extract(&mut m.attrs);
if info.opaque {
panic!("#[diplomat::opaque] not allowed on methods")
}
}
}
}
_ => (),
});
for custom_type in module.declared_types.values() {
custom_type.methods().iter().for_each(|m| {
new_contents.push(gen_custom_type_method(custom_type, m));
});
let destroy_ident = Ident::new(
format!("{}_destroy", custom_type.name()).as_str(),
Span::call_site(),
);
let type_ident = custom_type.name().to_syn();
let (lifetime_defs, lifetimes) = if let Some(lifetime_env) = custom_type.lifetimes() {
(
quote! { <#lifetime_env> },
lifetime_env.lifetimes_to_tokens(),
)
} else {
(quote! {}, quote! {})
};
let cfg = cfgs_to_stream(custom_type.cfg_attrs());
// for now, body is empty since all we need to do is drop the box
// TODO(#13): change to take a `*mut` and handle DST boxes appropriately
new_contents.push(Item::Fn(syn::parse_quote! {
#[no_mangle]
#cfg
extern "C" fn #destroy_ident#lifetime_defs(this: Box<#type_ident#lifetimes>) {}
}));
}
ItemMod {
attrs: input.attrs,
vis: input.vis,
mod_token: input.mod_token,
ident: input.ident,
content: Some((brace, new_contents)),
semi: input.semi,
unsafety: None,
}
}
/// Mark a module to be exposed through Diplomat-generated FFI.
#[proc_macro_attribute]
pub fn bridge(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let expanded = gen_bridge(parse_macro_input!(input));
proc_macro::TokenStream::from(expanded.to_token_stream())
}
/// Generate From and Into implementations for a Diplomat enum
///
/// This is invoked as `#[diplomat::enum_convert(OtherEnumName)]`
/// on a Diplomat enum. It will assume the other enum has exactly the same variants
/// and generate From and Into implementations using those. In case that enum is `#[non_exhaustive]`,
/// you may use `#[diplomat::enum_convert(OtherEnumName, needs_wildcard)]` to generate a panicky wildcard
/// branch. It is up to the library author to ensure the enums are kept in sync. You may use the `#[non_exhaustive_omitted_patterns]`
/// lint to enforce this.
#[proc_macro_attribute]
pub fn enum_convert(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
// proc macros handle compile errors by using special error tokens.
// In case of an error, we don't want the original code to go away too
// (otherwise that will cause more errors) so we hold on to it and we tack it in
// with no modifications below
let input_cached: proc_macro2::TokenStream = input.clone().into();
let expanded =
enum_convert::gen_enum_convert(parse_macro_input!(attr), parse_macro_input!(input));
let full = quote! {
#expanded
#input_cached
};
proc_macro::TokenStream::from(full.to_token_stream())
}
/// Generate conversions from inner types for opaque Diplomat types with a single field
///
/// This is invoked as `#[diplomat::transparent_convert]`
/// on an opaque Diplomat type. It will add `#[repr(transparent)]` and implement `pub(crate) fn transparent_convert()`
/// which allows constructing an `&Self` from a reference to the inner field.
#[proc_macro_attribute]
pub fn transparent_convert(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
// proc macros handle compile errors by using special error tokens.
// In case of an error, we don't want the original code to go away too
// (otherwise that will cause more errors) so we hold on to it and we tack it in
// with no modifications below
let input_cached: proc_macro2::TokenStream = input.clone().into();
let expanded = transparent_convert::gen_transparent_convert(parse_macro_input!(input));
let full = quote! {
#expanded
#input_cached
};
proc_macro::TokenStream::from(full.to_token_stream())
}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::io::{Read, Write};
use std::process::Command;
use quote::ToTokens;
use syn::parse_quote;
use tempfile::tempdir;
use super::gen_bridge;
fn rustfmt_code(code: &str) -> String {
let dir = tempdir().unwrap();
let file_path = dir.path().join("temp.rs");
let mut file = File::create(file_path.clone()).unwrap();
writeln!(file, "{code}").unwrap();
drop(file);
Command::new("rustfmt")
.arg(file_path.to_str().unwrap())
.spawn()
.unwrap()
.wait()
.unwrap();
let mut file = File::open(file_path).unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
drop(file);
dir.close().unwrap();
data
}
#[test]
fn method_taking_str() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
impl Foo {
pub fn from_str(s: &str) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn method_taking_slice() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
impl Foo {
pub fn from_slice(s: &[f64]) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn method_taking_mutable_slice() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
impl Foo {
pub fn fill_slice(s: &mut [f64]) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn mod_with_enum() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
enum Abc {
A,
B = 123,
}
impl Abc {
pub fn do_something(&self) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn mod_with_writeable_result() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
impl Foo {
pub fn to_string(&self, to: &mut DiplomatWriteable) -> Result<(), ()> {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn mod_with_rust_result() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
impl Foo {
pub fn bar(&self) -> Result<(), ()> {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn multilevel_borrows() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
#[diplomat::opaque]
struct Foo<'a>(&'a str);
#[diplomat::opaque]
struct Bar<'b, 'a: 'b>(&'b Foo<'a>);
struct Baz<'x, 'y> {
foo: &'y Foo<'x>,
}
impl<'a> Foo<'a> {
pub fn new(x: &'a str) -> Box<Foo<'a>> {
unimplemented!()
}
pub fn get_bar<'b>(&'b self) -> Box<Bar<'b, 'a>> {
unimplemented!()
}
pub fn get_baz<'b>(&'b self) -> Baz<'b, 'a> {
Bax { foo: self }
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn self_params() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
#[diplomat::opaque]
struct RefList<'a> {
data: &'a i32,
next: Option<Box<Self>>,
}
impl<'b> RefList<'b> {
pub fn extend(&mut self, other: &Self) -> Self {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn cfged_method() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
impl Foo {
#[cfg(feature = "foo")]
pub fn bar(s: u8) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
struct Foo {}
#[cfg(feature = "bar")]
impl Foo {
#[cfg(feature = "foo")]
pub fn bar(s: u8) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
#[test]
fn cfgd_struct() {
insta::assert_display_snapshot!(rustfmt_code(
&gen_bridge(parse_quote! {
mod ffi {
#[diplomat::opaque]
#[cfg(feature = "foo")]
struct Foo {}
#[cfg(feature = "foo")]
impl Foo {
pub fn bar(s: u8) {
unimplemented!()
}
}
}
})
.to_token_stream()
.to_string()
));
}
}

View File

@ -0,0 +1,23 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n #[diplomat :: opaque] #[cfg(feature = \"foo\")] struct Foo {}\n #[cfg(feature = \"foo\")] impl Foo\n { pub fn bar(s : u8) { unimplemented! () } }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[cfg(feature = "foo")]
struct Foo {}
#[cfg(feature = "foo")]
impl Foo {
pub fn bar(s: u8) {
unimplemented!()
}
}
#[no_mangle]
#[cfg(feature = "foo")]
extern "C" fn Foo_bar(s: u8) {
Foo::bar(s)
}
#[no_mangle]
#[cfg(feature = "foo")]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,24 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} #[cfg(feature = \"bar\")] impl Foo\n {\n #[cfg(feature = \"foo\")] pub fn bar(s : u8)\n { unimplemented! () }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
#[cfg(feature = "bar")]
impl Foo {
#[cfg(feature = "foo")]
pub fn bar(s: u8) {
unimplemented!()
}
}
#[no_mangle]
#[cfg(feature = "bar")]
#[cfg(feature = "foo")]
extern "C" fn Foo_bar(s: u8) {
Foo::bar(s)
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,22 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n {\n #[cfg(feature = \"foo\")] pub fn bar(s : u8)\n { unimplemented! () }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
#[cfg(feature = "foo")]
pub fn bar(s: u8) {
unimplemented!()
}
}
#[no_mangle]
#[cfg(feature = "foo")]
extern "C" fn Foo_bar(s: u8) {
Foo::bar(s)
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,20 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n { pub fn fill_slice(s : & mut [f64]) { unimplemented! () } }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
pub fn fill_slice(s: &mut [f64]) {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Foo_fill_slice(s_diplomat_data: *mut f64, s_diplomat_len: usize) {
Foo::fill_slice(unsafe { core::slice::from_raw_parts_mut(s_diplomat_data, s_diplomat_len) })
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,26 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n {\n pub fn make_uppercase(s : & mut str) { unimplemented! () }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
pub fn make_uppercase(s: &mut str) {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Foo_make_uppercase(s_diplomat_data: *mut u8, s_diplomat_len: usize) {
Foo::make_uppercase(unsafe {
core::str::from_utf8_mut(core::slice::from_raw_parts_mut(
s_diplomat_data,
s_diplomat_len,
))
.unwrap()
})
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,20 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n { pub fn from_slice(s : & [f64]) { unimplemented! () } }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
pub fn from_slice(s: &[f64]) {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Foo_from_slice(s_diplomat_data: *const f64, s_diplomat_len: usize) {
Foo::from_slice(unsafe { core::slice::from_raw_parts(s_diplomat_data, s_diplomat_len) })
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,23 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n { pub fn from_str(s : & str) { unimplemented! () } }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
pub fn from_str(s: &str) {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Foo_from_str(s_diplomat_data: *const u8, s_diplomat_len: usize) {
Foo::from_str(unsafe {
core::str::from_utf8(core::slice::from_raw_parts(s_diplomat_data, s_diplomat_len))
.unwrap()
})
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,23 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n enum Abc { A, B = 123, } impl Abc\n { pub fn do_something(& self) { unimplemented! () } }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
enum Abc {
A,
B = 123,
}
impl Abc {
pub fn do_something(&self) {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Abc_do_something(this: &Abc) {
this.do_something()
}
#[no_mangle]
extern "C" fn Abc_destroy(this: Box<Abc>) {}
}

View File

@ -0,0 +1,20 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n {\n pub fn bar(& self) -> Result < (), () >\n { unimplemented! () }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
pub fn bar(&self) -> Result<(), ()> {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Foo_bar(this: &Foo) -> diplomat_runtime::DiplomatResult<(), ()> {
this.bar().into()
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,25 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n struct Foo {} impl Foo\n {\n pub fn to_string(& self, to : & mut DiplomatWriteable) ->\n Result < (), () > { unimplemented! () }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
#[repr(C)]
struct Foo {}
impl Foo {
pub fn to_string(&self, to: &mut DiplomatWriteable) -> Result<(), ()> {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn Foo_to_string(
this: &Foo,
to: &mut diplomat_runtime::DiplomatWriteable,
) -> diplomat_runtime::DiplomatResult<(), ()> {
let ret = this.to_string(to);
to.flush();
ret.into()
}
#[no_mangle]
extern "C" fn Foo_destroy(this: Box<Foo>) {}
}

View File

@ -0,0 +1,45 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n #[diplomat :: opaque] struct Foo < 'a > (& 'a str) ;\n #[diplomat :: opaque] struct Bar < 'b, 'a : 'b >\n (& 'b Foo < 'a >) ; struct Baz < 'x, 'y >\n { foo : & 'y Foo < 'x >, } impl < 'a > Foo < 'a >\n {\n pub fn new(x : & 'a str) -> Box < Foo < 'a >>\n { unimplemented! () } pub fn get_bar < 'b > (& 'b self) ->\n Box < Bar < 'b, 'a >> { unimplemented! () } pub fn get_baz <\n 'b > (& 'b self) -> Baz < 'b, 'a > { Bax { foo : self } }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
struct Foo<'a>(&'a str);
struct Bar<'b, 'a: 'b>(&'b Foo<'a>);
#[repr(C)]
struct Baz<'x, 'y> {
foo: &'y Foo<'x>,
}
impl<'a> Foo<'a> {
pub fn new(x: &'a str) -> Box<Foo<'a>> {
unimplemented!()
}
pub fn get_bar<'b>(&'b self) -> Box<Bar<'b, 'a>> {
unimplemented!()
}
pub fn get_baz<'b>(&'b self) -> Baz<'b, 'a> {
Bax { foo: self }
}
}
#[no_mangle]
extern "C" fn Bar_destroy<'b, 'a: 'b>(this: Box<Bar<'b, 'a>>) {}
#[no_mangle]
extern "C" fn Baz_destroy<'x: 'y, 'y>(this: Box<Baz<'x, 'y>>) {}
#[no_mangle]
extern "C" fn Foo_new<'a>(x_diplomat_data: *const u8, x_diplomat_len: usize) -> Box<Foo<'a>> {
Foo::new(unsafe {
core::str::from_utf8(core::slice::from_raw_parts(x_diplomat_data, x_diplomat_len))
.unwrap()
})
}
#[no_mangle]
extern "C" fn Foo_get_bar<'a: 'b, 'b>(this: &'b Foo<'a>) -> Box<Bar<'b, 'a>> {
this.get_bar()
}
#[no_mangle]
extern "C" fn Foo_get_baz<'a: 'b, 'b>(this: &'b Foo<'a>) -> Baz<'b, 'a> {
this.get_baz()
}
#[no_mangle]
extern "C" fn Foo_destroy<'a>(this: Box<Foo<'a>>) {}
}

View File

@ -0,0 +1,22 @@
---
source: macro/src/lib.rs
expression: "rustfmt_code(&gen_bridge(parse_quote! {\n mod ffi\n {\n #[diplomat :: opaque] struct RefList < 'a >\n { data : & 'a i32, next : Option < Box < Self >>, } impl <\n 'b > RefList < 'b >\n {\n pub fn extend(& mut self, other : & Self) -> Self\n { unimplemented! () }\n }\n }\n }).to_token_stream().to_string())"
---
mod ffi {
struct RefList<'a> {
data: &'a i32,
next: Option<Box<Self>>,
}
impl<'b> RefList<'b> {
pub fn extend(&mut self, other: &Self) -> Self {
unimplemented!()
}
}
#[no_mangle]
extern "C" fn RefList_extend<'b>(this: &mut RefList<'b>, other: &RefList<'b>) -> RefList<'b> {
this.extend(other)
}
#[no_mangle]
extern "C" fn RefList_destroy<'a>(this: Box<RefList<'a>>) {}
}

View File

@ -0,0 +1,30 @@
use quote::quote;
use syn::*;
pub fn gen_transparent_convert(s: ItemStruct) -> proc_macro2::TokenStream {
let mut fields = s.fields.iter();
let field1 = if let Some(field1) = fields.next() {
&field1.ty
} else {
panic!("#[diplomat::transparent_convert] only allowed on structs with a single field")
};
if fields.next().is_some() {
panic!("#[diplomat::transparent_convert] only allowed on structs with a single field")
}
let struct_name = &s.ident;
let (impl_generics, ty_generics, _) = s.generics.split_for_impl();
let mut impl_generics: Generics = parse_quote!(#impl_generics);
let custom_lifetime: GenericParam = parse_quote!('transparent_convert_outer);
impl_generics.params.push(custom_lifetime);
quote! {
impl #impl_generics #struct_name #ty_generics {
// can potentially add transparent_convert_owned, _mut later
pub(crate) fn transparent_convert(from: &'transparent_convert_outer #field1) -> &'transparent_convert_outer Self {
unsafe {
&*(from as *const #field1 as *const Self)
}
}
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "diplomat_core"
version = "0.5.2"
authors = [
"Shadaj Laddad <shadaj@users.noreply.github.com>",
"Manish Goregaokar <manishsmail@gmail.com>",
"Quinn Okabayashi <QnnOkabayashi@users.noreply.github.com>",
]
description = "Shared utilities between Diplomat macros and code generation"
documentation = "https://docs.rs/diplomat_core/"
keywords = [
"ffi",
"codegen",
]
categories = ["development-tools"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rust-diplomat/diplomat"
[package.metadata.docs.rs]
all-features = true
[lib]
path = "src/lib.rs"
[dependencies]
lazy_static = "1.4.0"
proc-macro2 = "1.0.27"
quote = "1.0"
smallvec = "1.9.0"
[dependencies.displaydoc]
version = "0.2"
optional = true
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.strck_ident]
version = "0.1"
features = ["rust"]
[dependencies.syn]
version = "2"
features = [
"full",
"extra-traits",
]
[dev-dependencies]
insta = "1.7.1"
[features]
hir = []

View File

@ -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.

View File

@ -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.

View File

@ -0,0 +1,10 @@
//! This module contains utilities for dealing with Rust attributes
use syn::Attribute;
pub(crate) fn extract_cfg_attrs(attrs: &[Attribute]) -> impl Iterator<Item = String> + '_ {
attrs
.iter()
.filter(|&a| a.path().is_ident("cfg"))
.map(|a| quote::quote!(#a).to_string())
}

View File

@ -0,0 +1,431 @@
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>);
/// The type of markdown generated by [`Docs::to_markdown()`]
///
/// Note that this only controls markdown generated by this code. Existing markdown
/// in the Rust documentation will not be sanitized in any way.
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum MarkdownStyle {
/// Regular markdown with no specific extensions, compatible with most common flavors
Normal,
/// Markdown that can be losslessly converted to ReStructuredText
RstCompat,
}
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, style: MarkdownStyle) -> String {
use std::fmt::Write;
let mut lines = self.0.clone();
let mut has_compact = false;
let backtick = if style == MarkdownStyle::RstCompat {
""
} else {
"`"
};
for rust_link in &self.1 {
if rust_link.display == RustLinkDisplay::Compact {
has_compact = true;
} else if rust_link.display == RustLinkDisplay::Normal {
write!(
lines,
"\n\nSee the [Rust documentation for {backtick}{name}{backtick}]({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 {
write!(lines, "\n\n 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)]
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)]
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)]
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"
);
}

View File

@ -0,0 +1,112 @@
use serde::{Deserialize, Serialize};
use super::docs::Docs;
use super::{attrs, Ident, Method};
use quote::ToTokens;
/// A fieldless enum declaration in an FFI module.
#[derive(Clone, Serialize, Deserialize, Debug, Hash, PartialEq, Eq)]
pub struct Enum {
pub name: Ident,
pub docs: Docs,
/// A list of variants of the enum. (name, discriminant, docs)
pub variants: Vec<(Ident, isize, Docs)>,
pub methods: Vec<Method>,
pub cfg_attrs: Vec<String>,
}
impl From<&syn::ItemEnum> for Enum {
/// Extract an [`Enum`] metadata value from an AST node.
fn from(enm: &syn::ItemEnum) -> 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 cfg_attrs = attrs::extract_cfg_attrs(&enm.attrs).collect();
Enum {
name: (&enm.ident).into(),
docs: Docs::from_attrs(&enm.attrs),
variants: enm
.variants
.iter()
.map(|v| {
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;
(
(&v.ident).into(),
new_discriminant,
Docs::from_attrs(&v.attrs),
)
})
.collect(),
methods: vec![],
cfg_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::from(&syn::parse_quote! {
/// Some docs.
#[diplomat::rust_link(foo::Bar, Enum)]
enum MyLocalEnum {
Abc,
/// Some more docs.
Def
}
}));
});
}
#[test]
fn enum_with_discr() {
let mut settings = Settings::new();
settings.set_sort_maps(true);
settings.bind(|| {
insta::assert_yaml_snapshot!(Enum::from(&syn::parse_quote! {
/// Some docs.
#[diplomat::rust_link(foo::Bar, Enum)]
enum DiscriminantedEnum {
Abc = -1,
Def = 0,
Ghi = 1,
Jkl = 2,
}
}));
});
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,573 @@
use proc_macro2::Span;
use quote::{quote, ToTokens};
use serde::{Deserialize, Serialize};
use std::fmt;
use super::{Docs, Ident, Param, SelfParam, 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(&param.ty, None);
}
if let Some(return_type) = return_type {
this.extend_implicit_lifetime_bounds(return_type, None);
}
this
}
/// 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)]) -> 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)]
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((&lt.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[..],
}
}
}

View File

@ -0,0 +1,592 @@
use serde::{Deserialize, Serialize};
use std::ops::ControlFlow;
use super::attrs;
use super::docs::Docs;
use super::{Ident, Lifetime, LifetimeEnv, Mutability, Path, PathType, TypeName, ValidityError};
use crate::Env;
/// 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, Deserialize, Debug)]
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 FFI function wrapping around the method.
pub full_path_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 cfg_attrs: Vec<String>,
}
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>,
cfg_attrs: &[String],
) -> Method {
let self_ident = self_path_type.path.elements.last().unwrap();
let method_ident = &m.sig.ident;
let extern_ident = syn::Ident::new(
format!("{self_ident}_{method_ident}").as_str(),
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(),
);
let mut cfg_attrs = cfg_attrs.to_owned();
cfg_attrs.extend(attrs::extract_cfg_attrs(&m.attrs));
Method {
name: Ident::from(method_ident),
docs: Docs::from_attrs(&m.attrs),
full_path_name: Ident::from(&extern_ident),
self_param,
params: all_params,
return_type: return_ty,
lifetime_env,
cfg_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![])
}
}
/// Performs type-specific validity checks (see [TypeName::check_validity()])
pub fn check_validity<'a>(
&'a self,
in_path: &Path,
env: &Env,
errors: &mut Vec<ValidityError>,
) {
// validity check that if the self type is nonopaque, that it is
// behind a reference
if let Some(ref self_param) = self.self_param {
self_param
.to_typename()
.check_validity(in_path, env, errors);
}
for m in self.params.iter() {
// Do we need to check the validity of the input types?
m.ty.check_validity(in_path, env, errors);
}
if let Some(ref t) = self.return_type {
t.check_return_type_validity(in_path, env, errors);
}
}
/// Checks whether the method qualifies for special writeable handling.
/// To qualify, a method must:
/// - not return any value
/// - have the last argument be an `&mut diplomat_runtime::DiplomatWriteable`
///
/// Typically, methods of this form will be transformed in the bindings to a
/// method that doesn't take the writeable as an argument but instead creates
/// one locally and just returns the final string.
pub fn is_writeable_out(&self) -> bool {
let return_compatible = self
.return_type
.as_ref()
.map(|return_type| match return_type {
TypeName::Unit => true,
TypeName::Result(ok, _, _) => {
matches!(ok.as_ref(), TypeName::Unit)
}
_ => false,
})
.unwrap_or(true);
return_compatible && self.params.last().map(Param::is_writeable).unwrap_or(false)
}
/// Checks if any parameters are writeable (regardless of other compatibilities for writeable output)
pub fn has_writeable_param(&self) -> bool {
self.params.iter().any(|p| p.is_writeable())
}
/// Returns the documentation block
pub fn docs(&self) -> &Docs {
&self.docs
}
}
/// The `self` parameter taken by a [`Method`].
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
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,
}
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,
}
}
}
/// A parameter taken by a [`Method`], not including `self`.
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
pub struct Param {
/// The name of the parameter in the original method declaration.
pub name: Ident,
/// The type of the parameter.
pub ty: TypeName,
}
impl Param {
/// Check if this parameter is a Writeable
pub fn is_writeable(&self) -> bool {
match self.ty {
TypeName::Reference(_, Mutability::Mutable, ref w) => **w == TypeName::Writeable,
_ => 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"),
};
Param {
name: (&ident.ident).into(),
ty: TypeName::from_syn(&t.ty, Some(self_path_type)),
}
}
}
/// 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.
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_map(|&(param, ltk)| {
(ltk == LifetimeKind::ReturnValue).then(|| &param.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_map(|&(param, ltk)| (ltk == LifetimeKind::Static).then(|| &param.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, _)| &param.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::Ident;
use super::{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,
&[]
));
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,
&[]
));
}
#[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,
&[]
));
}
#[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,
&[]
));
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,
&[]
));
}
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,
&[]
);
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!()
}
}
}
}

View File

@ -0,0 +1,37 @@
/// 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(crate) mod attrs;
mod methods;
pub use methods::{BorrowedParams, Method, Param, SelfParam};
mod modules;
pub use modules::{File, Module};
mod structs;
pub use structs::{OpaqueStruct, Struct};
mod enums;
pub use enums::Enum;
mod types;
pub use types::{
CustomType, LifetimeOrigin, ModSymbol, Mutability, PathType, PrimitiveType, TypeName,
};
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, MarkdownStyle, RustLink, RustLinkDisplay};
mod validity;
pub use validity::ValidityError;

View File

@ -0,0 +1,406 @@
use std::collections::{BTreeMap, HashSet};
use std::fmt::Write as _;
use quote::ToTokens;
use serde::{Deserialize, Serialize};
use syn::{ImplItem, Item, ItemMod, UseTree, Visibility};
use super::{
attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability, OpaqueStruct, Path, PathType,
RustLink, Struct, ValidityError,
};
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, Deserialize, Debug)]
pub struct Module {
pub name: Ident,
pub imports: Vec<(Path, Ident)>,
pub declared_types: BTreeMap<Ident, CustomType>,
pub sub_modules: Vec<Module>,
}
impl Module {
pub fn check_validity(&self, in_path: &Path, env: &Env, errors: &mut Vec<ValidityError>) {
self.declared_types.values().for_each(|t| {
t.check_validity(&in_path.sub_path(self.name.clone()), env, errors);
});
self.sub_modules.iter().for_each(|t| {
t.check_validity(&in_path.sub_path(self.name.clone()), env, errors);
});
}
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::default();
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");
}
});
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 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");
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)),
Ok(Some(DiplomatStructAttribute::Out)) => {
CustomType::Struct(Struct::new(strct, true))
}
Ok(Some(DiplomatStructAttribute::Opaque)) => {
CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Immutable))
}
Ok(Some(DiplomatStructAttribute::OpaqueMut)) => {
CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Mutable))
}
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 {
custom_types_by_name
.insert((&enm.ident).into(), CustomType::Enum(Enum::from(enm)));
}
}
Item::Impl(imp) => {
if analyze_types {
assert!(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 cfg_attrs: Vec<_> = attrs::extract_cfg_attrs(&imp.attrs).collect();
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), &cfg_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));
}
_ => {}
});
Module {
name: (&input.ident).into(),
imports,
declared_types: custom_types_by_name,
sub_modules,
}
}
}
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, Deserialize, Clone, Debug)]
pub struct File {
pub modules: BTreeMap<String, Module>,
}
impl File {
/// Performs all necessary validity checks and returns any errors
///
/// Environment should be passed in from `.all_types()`
pub fn check_validity(&self, env: &Env) -> Vec<ValidityError> {
let mut errors = vec![];
self.modules
.values()
.for_each(|t| t.check_validity(&Path::empty(), env, &mut errors));
errors
}
/// 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::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::*;
}
}));
});
}
}

View File

@ -0,0 +1,76 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use super::Ident;
#[derive(Hash, Eq, PartialEq, Deserialize, Serialize, Clone, Debug, Ord, PartialOrd)]
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(),
}
}
}

View File

@ -0,0 +1,33 @@
---
source: core/src/ast/enums.rs
expression: "Enum::from(&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 })"
---
name: DiscriminantedEnum
docs:
- Some docs.
- - path:
elements:
- foo
- Bar
typ: Enum
display: Normal
variants:
- - Abc
- -1
- - ""
- []
- - Def
- 0
- - ""
- []
- - Ghi
- 1
- - ""
- []
- - Jkl
- 2
- - ""
- []
methods: []
cfg_attrs: []

View File

@ -0,0 +1,25 @@
---
source: core/src/ast/enums.rs
expression: "Enum::from(&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 })"
---
name: MyLocalEnum
docs:
- Some docs.
- - path:
elements:
- foo
- Bar
typ: Enum
display: Normal
variants:
- - Abc
- 0
- - ""
- []
- - Def
- 1
- - Some more docs.
- []
methods: []
cfg_attrs: []

View File

@ -0,0 +1,32 @@
---
source: core/src/ast/methods.rs
expression: "Method::from_syn(&syn::parse_quote! {\n /// 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, &[])"
---
name: foo
docs:
- Some docs.
- - path:
elements:
- foo
- Bar
- batz
typ: FnInStruct
display: Normal
full_path_name: MyStructContainingMethod_foo
self_param: ~
params:
- name: x
ty:
Primitive: u64
- name: y
ty:
Named:
path:
elements:
- MyCustomStruct
lifetimes: []
return_type: ~
lifetime_env: {}
cfg_attrs:
- "# [cfg (any (feature = \"foo\" , not (feature = \"bar\")))]"

View File

@ -0,0 +1,40 @@
---
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, &[])"
---
name: foo
docs:
- ""
- - path:
elements:
- foo
- Bar
- batz
typ: FnInStruct
display: Normal
full_path_name: MyStructContainingMethod_foo
self_param:
reference:
- Anonymous
- Mutable
path_type:
path:
elements:
- MyStructContainingMethod
lifetimes: []
params:
- name: x
ty:
Primitive: u64
- name: y
ty:
Named:
path:
elements:
- MyCustomStruct
lifetimes: []
return_type:
Primitive: u64
lifetime_env: {}
cfg_attrs: []

View File

@ -0,0 +1,33 @@
---
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, &[])"
---
name: foo
docs:
- ""
- []
full_path_name: MyStructContainingMethod_foo
self_param:
reference:
- Anonymous
- Immutable
path_type:
path:
elements:
- MyStructContainingMethod
lifetimes: []
params:
- name: x
ty:
Primitive: u64
- name: y
ty:
Named:
path:
elements:
- MyCustomStruct
lifetimes: []
return_type: ~
lifetime_env: {}
cfg_attrs: []

View File

@ -0,0 +1,32 @@
---
source: core/src/ast/methods.rs
expression: "Method::from_syn(&syn::parse_quote! {\n /// Some docs.\n /// Some more docs.\n ///\n /// 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, &[])"
---
name: foo
docs:
- "Some docs.\nSome more docs.\n\nEven more docs."
- - path:
elements:
- foo
- Bar
- batz
typ: FnInEnum
display: Normal
full_path_name: MyStructContainingMethod_foo
self_param: ~
params:
- name: x
ty:
Primitive: u64
- name: y
ty:
Named:
path:
elements:
- MyCustomStruct
lifetimes: []
return_type:
Primitive: u64
lifetime_env: {}
cfg_attrs: []

View File

@ -0,0 +1,31 @@
---
source: core/src/ast/methods.rs
expression: "Method::from_syn(&syn::parse_quote! {\n /// 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, &[])"
---
name: foo
docs:
- Some docs.
- - path:
elements:
- foo
- Bar
- batz
typ: FnInStruct
display: Normal
full_path_name: MyStructContainingMethod_foo
self_param: ~
params:
- name: x
ty:
Primitive: u64
- name: y
ty:
Named:
path:
elements:
- MyCustomStruct
lifetimes: []
return_type: ~
lifetime_env: {}
cfg_attrs: []

View File

@ -0,0 +1,27 @@
---
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
cfg_attrs: []
sub_modules: []
other:
name: other
imports: []
declared_types: {}
sub_modules: []

View File

@ -0,0 +1,30 @@
---
source: core/src/ast/modules.rs
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:
- ""
- []
full_path_name: Foo_pub_fn
self_param: ~
params: []
return_type: ~
lifetime_env: {}
cfg_attrs: []
output_only: false
cfg_attrs: []
sub_modules: []

View File

@ -0,0 +1,121 @@
---
source: core/src/ast/modules.rs
expression: "Module::from_syn(&syn::parse_quote! {\n mod ffi\n {\n struct NonOpaqueStruct\n { a : i32, b : Box < NonOpaqueStruct > } impl\n NonOpaqueStruct\n {\n pub fn new(x : i32) -> NonOpaqueStruct\n { unimplemented! () ; } pub fn\n 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! () ; }\n pub 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:
- ""
- []
full_path_name: NonOpaqueStruct_new
self_param: ~
params:
- name: x
ty:
Primitive: i32
return_type:
Named:
path:
elements:
- NonOpaqueStruct
lifetimes: []
lifetime_env: {}
cfg_attrs: []
- name: set_a
docs:
- ""
- []
full_path_name: NonOpaqueStruct_set_a
self_param:
reference:
- Anonymous
- Mutable
path_type:
path:
elements:
- NonOpaqueStruct
lifetimes: []
params:
- name: new_a
ty:
Primitive: i32
return_type: ~
lifetime_env: {}
cfg_attrs: []
output_only: false
cfg_attrs: []
OpaqueStruct:
Opaque:
name: OpaqueStruct
docs:
- ""
- []
lifetimes: {}
methods:
- name: new
docs:
- ""
- []
full_path_name: OpaqueStruct_new
self_param: ~
params: []
return_type:
Box:
Named:
path:
elements:
- OpaqueStruct
lifetimes: []
lifetime_env: {}
cfg_attrs: []
- name: get_string
docs:
- ""
- []
full_path_name: OpaqueStruct_get_string
self_param:
reference:
- Anonymous
- Immutable
path_type:
path:
elements:
- OpaqueStruct
lifetimes: []
params: []
return_type:
Named:
path:
elements:
- String
lifetimes: []
lifetime_env: {}
cfg_attrs: []
mutability: Immutable
cfg_attrs: []
sub_modules: []

View File

@ -0,0 +1,32 @@
---
source: core/src/ast/structs.rs
expression: "Struct::new(&syn::parse_quote! {\n /// Some docs.\n #[diplomat :: rust_link(foo :: Bar, Struct)] struct\n MyLocalStruct { a : i32, b : Box < MyLocalStruct > }\n }, true)"
---
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
cfg_attrs: []

View File

@ -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: []

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,20 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! {\n 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
- true

View File

@ -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

View File

@ -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: []

View File

@ -0,0 +1,7 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { Box < i32 > }, None)"
---
Box:
Primitive: i32

View File

@ -0,0 +1,10 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { MyLocalStruct }, None)"
---
Named:
path:
elements:
- MyLocalStruct
lifetimes: []

View File

@ -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: []

View File

@ -0,0 +1,7 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { Option < i32 > }, None)"
---
Option:
Primitive: i32

View File

@ -0,0 +1,6 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { usize }, None)"
---
Primitive: usize

View File

@ -0,0 +1,6 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { bool }, None)"
---
Primitive: bool

View File

@ -0,0 +1,6 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { i32 }, None)"
---
Primitive: i32

View File

@ -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: []

View File

@ -0,0 +1,9 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { & i32 }, None)"
---
Reference:
- Anonymous
- Immutable
- Primitive: i32

View File

@ -0,0 +1,13 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! {\n DiplomatResult < (), MyLocalStruct >\n }, None)"
---
Result:
- Unit
- Named:
path:
elements:
- MyLocalStruct
lifetimes: []
- false

View File

@ -0,0 +1,13 @@
---
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
- true

View File

@ -0,0 +1,13 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! { Result < (), MyLocalStruct > }, None)"
---
Result:
- Unit
- Named:
path:
elements:
- MyLocalStruct
lifetimes: []
- true

View File

@ -0,0 +1,13 @@
---
source: core/src/ast/types.rs
expression: "TypeName::from_syn(&syn::parse_quote! {\n DiplomatResult < MyLocalStruct, i32 >\n }, None)"
---
Result:
- Named:
path:
elements:
- MyLocalStruct
lifetimes: []
- Primitive: i32
- false

View File

@ -0,0 +1,9 @@
---
source: core/src/ast/validity.rs
expression: output
---
A non-opaque type was found behind a Box or reference, these can only be handled by-move as they get converted at the FFI boundary: NonOpaque
A non-opaque type was found behind a Box or reference, these can only be handled by-move as they get converted at the FFI boundary: NonOpaque
A non-opaque type was found behind a Box or reference, these can only be handled by-move as they get converted at the FFI boundary: NonOpaque
A non-opaque type was found behind a Box or reference, these can only be handled by-move as they get converted at the FFI boundary: NonOpaque

View File

@ -0,0 +1,7 @@
---
source: core/src/ast/validity.rs
expression: output
---
An opaque type crossed the FFI boundary as a value: OpaqueStruct
An opaque type crossed the FFI boundary as a value: OpaqueStruct

View File

@ -0,0 +1,6 @@
---
source: core/src/ast/validity.rs
expression: output
---
A non-opaque zero-sized struct or enum has been defined: ffi::NonOpaqueStruct

View File

@ -0,0 +1,7 @@
---
source: core/src/ast/validity.rs
expression: output
---
An opaque type crossed the FFI boundary as a value: MyOpaqueStruct
An opaque type crossed the FFI boundary as a value: MyOpaqueStruct

View File

@ -0,0 +1,9 @@
---
source: core/src/ast/validity.rs
expression: output
---
A non-reference type was found inside an Option<T>: Option<u8>
A non-reference type was found inside an Option<T>: Option<Option<u16>>
A non-reference type was found inside an Option<T>: Option<char>
A non-reference type was found inside an Option<T>: Option<u16>

View File

@ -0,0 +1,5 @@
---
source: core/src/ast/validity.rs
expression: output
---

View File

@ -0,0 +1,7 @@
---
source: core/src/ast/validity.rs
expression: output
---
A non-opaque zero-sized struct or enum has been defined: ffi::OpaqueEnum
A non-opaque zero-sized struct or enum has been defined: ffi::OpaqueStruct

View File

@ -0,0 +1,109 @@
use serde::{Deserialize, 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, Deserialize, Debug)]
pub struct Struct {
pub name: Ident,
pub docs: Docs,
pub lifetimes: LifetimeEnv,
pub fields: Vec<(Ident, TypeName, Docs)>,
pub methods: Vec<Method>,
pub output_only: bool,
pub cfg_attrs: Vec<String>,
}
impl Struct {
/// Extract a [`Struct`] metadata value from an AST node.
pub fn new(strct: &syn::ItemStruct, output_only: bool) -> 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)
})
.collect();
let lifetimes = LifetimeEnv::from_struct_item(strct, &fields[..]);
let cfg_attrs = attrs::extract_cfg_attrs(&strct.attrs).collect();
Struct {
name: (&strct.ident).into(),
docs: Docs::from_attrs(&strct.attrs),
lifetimes,
fields,
methods: vec![],
output_only,
cfg_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, Deserialize, Debug, Hash, PartialEq, Eq)]
pub struct OpaqueStruct {
pub name: Ident,
pub docs: Docs,
pub lifetimes: LifetimeEnv,
pub methods: Vec<Method>,
pub mutability: Mutability,
pub cfg_attrs: Vec<String>,
}
impl OpaqueStruct {
/// Extract a [`OpaqueStruct`] metadata value from an AST node.
pub fn new(strct: &syn::ItemStruct, mutability: Mutability) -> Self {
let cfg_attrs = attrs::extract_cfg_attrs(&strct.attrs).collect();
OpaqueStruct {
name: Ident::from(&strct.ident),
docs: Docs::from_attrs(&strct.attrs),
lifetimes: LifetimeEnv::from_struct_item(strct, &[]),
methods: vec![],
mutability,
cfg_attrs,
}
}
}
#[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
));
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,224 @@
use super::{Ident, Path, TypeName};
#[cfg_attr(feature = "displaydoc", derive(displaydoc::Display))]
#[derive(Debug, Clone)]
pub enum ValidityError {
/// An oqaue type crosses the FFI boundary as a value.
#[cfg_attr(
feature = "displaydoc",
displaydoc("An opaque type crossed the FFI boundary as a value: {0}")
)]
OpaqueAsValue(TypeName),
/// A non-oquare zero-sized struct or enum has been defined.
#[cfg_attr(
feature = "displaydoc",
displaydoc("A non-opaque zero-sized struct or enum has been defined: {0}")
)]
NonOpaqueZST(Path),
/// A non-opaque type was found behind a `Box` or reference.
#[cfg_attr(
feature = "displaydoc",
displaydoc(
"A non-opaque type was found behind a Box or reference, these can \
only be handled by-move as they get converted at the FFI boundary: {0}"
)
)]
NonOpaqueBehindRef(TypeName),
/// A non-reference type was found inside an `Option<T>`.
#[cfg_attr(
feature = "displaydoc",
displaydoc("A non-reference type was found inside an Option<T>: {0}")
)]
OptionNotContainingPointer(TypeName),
/// A return type contains elided lifetimes.
#[cfg_attr(
feature = "displaydoc",
displaydoc("A return type contains elided lifetimes, which aren't yet supported: {sub_type} in {full_type}")
)]
LifetimeElisionInReturn {
full_type: TypeName,
sub_type: TypeName,
},
/// An alias or submodule was found instead of a custom type.
#[cfg_attr(
feature = "displaydoc",
displaydoc("An alias or submodule was found instead of a custom type with the name {0}.")
)]
PathTypeNameConflict(Ident),
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
macro_rules! uitest_validity {
($($file:tt)*) => {
let parsed: syn::File = syn::parse_quote! { $($file)* };
let custom_types = crate::ast::File::from(&parsed);
let env = custom_types.all_types();
let errors = custom_types.check_validity(&env);
let mut output = String::new();
for error in errors {
write!(&mut output, "{}\n", error).unwrap();
}
insta::with_settings!({}, {
insta::assert_display_snapshot!(output)
});
}
}
#[test]
fn test_opaque_ffi() {
uitest_validity! {
#[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_validity! {
#[diplomat::bridge]
mod ffi {
struct NonOpaqueStruct {}
impl NonOpaqueStruct {
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_validity! {
#[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_validity! {
#[diplomat::bridge]
mod ffi {
struct OpaqueStruct;
enum OpaqueEnum {}
}
};
}
#[test]
fn option_invalid() {
uitest_validity! {
#[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<char>, u8>) {
}
pub fn do_thing2(opt: Option<u16>) {
}
}
}
};
}
#[test]
fn option_valid() {
uitest_validity! {
#[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_validity! {
#[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> {}
}
}
};
}
}

View File

@ -0,0 +1,88 @@
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(Default, Clone)]
pub struct ModuleEnv {
pub(crate) module: BTreeMap<Ident, ModSymbol>,
}
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 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]
}
}

View File

@ -0,0 +1,166 @@
//! Type definitions for structs, output structs, opaque structs, and enums.
use super::{Everywhere, IdentBuf, Method, OutputOnly, TyPosition, Type};
use crate::ast::Docs;
pub enum ReturnableStructDef<'tcx> {
Struct(&'tcx StructDef),
OutStruct(&'tcx OutStructDef),
}
#[derive(Copy, Clone, Debug)]
pub enum TypeDef<'tcx> {
Struct(&'tcx StructDef),
OutStruct(&'tcx OutStructDef),
Opaque(&'tcx OpaqueDef),
Enum(&'tcx EnumDef),
}
/// 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)]
pub struct StructDef<P: TyPosition = Everywhere> {
pub docs: Docs,
pub name: IdentBuf,
pub fields: Vec<StructField<P>>,
pub methods: Vec<Method>,
}
/// 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)]
pub struct OpaqueDef {
pub docs: Docs,
pub name: IdentBuf,
pub methods: Vec<Method>,
}
/// The enum type.
#[derive(Debug)]
pub struct EnumDef {
pub docs: Docs,
pub name: IdentBuf,
pub variants: Vec<EnumVariant>,
pub methods: Vec<Method>,
}
/// A field on a [`OutStruct`]s.
pub type OutStructField = StructField<OutputOnly>;
/// A field on a [`Struct`]s.
#[derive(Debug)]
pub struct StructField<P: TyPosition = Everywhere> {
pub docs: Docs,
pub name: IdentBuf,
pub ty: Type<P>,
}
/// A variant of an [`Enum`].
#[derive(Debug)]
pub struct EnumVariant {
pub docs: Docs,
pub name: IdentBuf,
pub discriminant: isize,
}
impl<P: TyPosition> StructDef<P> {
pub(super) fn new(
docs: Docs,
name: IdentBuf,
fields: Vec<StructField<P>>,
methods: Vec<Method>,
) -> Self {
Self {
docs,
name,
fields,
methods,
}
}
}
impl OpaqueDef {
pub(super) fn new(docs: Docs, name: IdentBuf, methods: Vec<Method>) -> Self {
Self {
docs,
name,
methods,
}
}
}
impl EnumDef {
pub(super) fn new(
docs: Docs,
name: IdentBuf,
variants: Vec<EnumVariant>,
methods: Vec<Method>,
) -> Self {
Self {
docs,
name,
variants,
methods,
}
}
}
impl<'a> From<&'a StructDef> for TypeDef<'a> {
fn from(x: &'a StructDef) -> Self {
TypeDef::Struct(x)
}
}
impl<'a> From<&'a OutStructDef> for TypeDef<'a> {
fn from(x: &'a OutStructDef) -> Self {
TypeDef::OutStruct(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,
}
}
}

View File

@ -0,0 +1,549 @@
//! 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::{
lower_ident, Lifetime, LifetimeEnv, LoweringError, MaybeStatic, MethodLifetime, TypeLifetime,
TypeLifetimes,
};
use crate::ast;
use smallvec::SmallVec;
/// Lower [`ast::Lifetime`]s to [`TypeLifetime`]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<TypeLifetime>;
/// Lowers a slice of [`ast::Lifetime`]s by calling
/// [`LifetimeLowerer::lower_lifetime`] repeatedly.
fn lower_lifetimes(&mut self, lifetimes: &[ast::Lifetime]) -> TypeLifetimes {
TypeLifetimes::from_fn(lifetimes, |lifetime| self.lower_lifetime(lifetime))
}
/// 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.
fn lower_generics(&mut self, lifetimes: &[ast::Lifetime], is_self: bool) -> TypeLifetimes;
}
/// 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<TypeLifetime>),
/// One param contains a borrow, elision allowed.
OneParam(MaybeStatic<TypeLifetime>),
/// Multiple borrows and no self borrow, no elision.
MultipleBorrows,
}
impl ElisionSource {
/// Potentially transition to a new state.
fn visit_lifetime(&mut self, lifetime: MaybeStatic<TypeLifetime>) {
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<TypeLifetimes>,
nodes: SmallVec<[Lifetime; 4]>,
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 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 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 struct ReturnLifetimeLowerer<'ast> {
elision_source: ElisionSource,
base: BaseLifetimeLowerer<'ast>,
}
impl<'ast> BaseLifetimeLowerer<'ast> {
/// Returns a [`TypeLifetime`] representing a new anonymous lifetime, and
/// pushes it to the nodes vector.
fn new_elided(&mut self) -> TypeLifetime {
let index = self.num_lifetimes;
self.num_lifetimes += 1;
TypeLifetime::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<TypeLifetime> {
match lifetime {
ast::Lifetime::Static => MaybeStatic::Static,
ast::Lifetime::Named(named) => {
MaybeStatic::NonStatic(TypeLifetime::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]) -> TypeLifetimes {
if let Some(lifetimes) = &self.self_lifetimes {
lifetimes.clone()
} else {
let lifetimes = TypeLifetimes::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,
errors: &mut Vec<LoweringError>,
) -> Option<Self> {
let mut hir_nodes = Some(SmallVec::new());
for ast_node in lifetime_env.nodes.iter() {
let lifetime = lower_ident(ast_node.lifetime.name(), "named lifetime", errors);
match (lifetime, &mut hir_nodes) {
(Some(lifetime), Some(hir_nodes)) => {
hir_nodes.push(Lifetime::new(
lifetime,
ast_node
.longer
.iter()
.map(|i| MethodLifetime::new(*i))
.collect(),
ast_node
.shorter
.iter()
.map(|i| MethodLifetime::new(*i))
.collect(),
));
}
_ => hir_nodes = None,
}
}
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<TypeLifetime>, 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<TypeLifetime> {
let lifetime = self.base.lower_lifetime(borrow);
self.elision_source.visit_lifetime(lifetime);
lifetime
}
fn lower_generics(&mut self, lifetimes: &[ast::Lifetime], is_self: bool) -> TypeLifetimes {
if is_self {
self.base.self_lifetimes_or_new(lifetimes)
} else {
self.lower_lifetimes(lifetimes)
}
}
}
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<TypeLifetime> {
match borrow {
ast::Lifetime::Static => MaybeStatic::Static,
ast::Lifetime::Named(named) => {
MaybeStatic::NonStatic(TypeLifetime::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], is_self: bool) -> TypeLifetimes {
if is_self {
self.base.self_lifetimes_or_new(lifetimes)
} else {
self.lower_lifetimes(lifetimes)
}
}
}
impl LifetimeLowerer for &ast::LifetimeEnv {
fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<TypeLifetime> {
match lifetime {
ast::Lifetime::Static => MaybeStatic::Static,
ast::Lifetime::Named(named) => {
MaybeStatic::NonStatic(TypeLifetime::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], _: bool) -> TypeLifetimes {
self.lower_lifetimes(lifetimes)
}
}
// 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_ident::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::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 tcx = crate::hir::TypeContext::from_ast(&env).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: &'a str,
}
struct Struct<'a> {
s: &'a str,
}
#[diplomat::out]
struct OutStruct<'a> {
inner: Box<Opaque<'a>>,
}
impl<'a> OutStruct<'a> {
pub fn new(s: &'a str) -> Self {
Self { inner: Box::new(Opaque { s }) }
}
}
impl<'a> Struct<'a> {
pub fn rustc_elision(self, s: &str) -> &str {
s
}
}
}
}
}
#[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: &'static str,
inner: Inner<'q>,
}
struct Inner<'a> {
more_data: &'a str,
}
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 str) -> 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::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);
})
}
}

View File

@ -0,0 +1,284 @@
//! Lifetime information for types.
#![allow(dead_code)]
use super::IdentBuf;
use crate::ast;
use smallvec::{smallvec, SmallVec};
/// Convenience const representing the number of lifetimes a [`LifetimeEnv`]
/// can hold inline before needing to dynamically allocate.
const INLINE_NUM_LIFETIMES: usize = 4;
// TODO(Quinn): This type is going to mainly be recycled from `ast::LifetimeEnv`.
// Not fully sure how that will look like yet, but the ideas of what this will do
// is basically the same.
#[derive(Debug)]
pub struct LifetimeEnv {
/// List of named lifetimes in scope of the method, in the form of an
/// adjacency matrix.
nodes: SmallVec<[Lifetime; INLINE_NUM_LIFETIMES]>,
/// 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 [`MethodLfetimes`] with
/// `num_lifetimes` entries, then `TypeLifetime`s that convert into
/// `MethodLifetime`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 `TypeLifetime` about which kind it refers to.
num_lifetimes: usize,
}
/// A lifetime in a [`LifetimeEnv`], which keeps track of which lifetimes it's
/// longer and shorter than.
#[derive(Debug)]
pub(super) struct Lifetime {
ident: IdentBuf,
longer: SmallVec<[MethodLifetime; 2]>,
shorter: SmallVec<[MethodLifetime; 2]>,
}
impl Lifetime {
/// Returns a new [`Lifetime`].
pub(super) fn new(
ident: IdentBuf,
longer: SmallVec<[MethodLifetime; 2]>,
shorter: SmallVec<[MethodLifetime; 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(MethodLifetime),
{
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: MethodLifetime) {
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 `TypeLifetime` and `MethodLifetime`, indicating that it may
/// be the `'static` lifetime.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
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 signature.
///
/// This type can be mapped to a [`MethodLifetime`] by using the
/// [`TypeLifetime::as_method_lifetime`] method.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TypeLifetime(usize);
/// A set of lifetimes that exist as generic arguments on [`StructPath`]s,
/// [`OutStructPath`]s, and [`OpaquePath`]s.
///
/// By itself, `TypeLifetimes` isn't very useful. However, it can be combined with
/// a [`MethodLifetimes`] using [`TypeLifetimes::as_method_lifetimes`] to get the lifetimes
/// in the scope of a method it appears in.
///
/// [`StructPath`]: super::StructPath
/// [`OutStructPath`]: super::OutStructPath
/// [`OpaquePath`]: super::OpaquePath
#[derive(Clone, Debug)]
pub struct TypeLifetimes {
indices: SmallVec<[MaybeStatic<TypeLifetime>; 2]>,
}
/// A lifetime that exists as part of a method signature, e.g. `'a` or an
/// anonymous lifetime.
///
/// This type is intended to be used as a key into a map to keep track of which
/// borrowed fields depend on which method lifetimes.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct MethodLifetime(usize);
/// Map a lifetime in a nested struct to the original lifetime defined
/// in the method that it refers to.
pub struct MethodLifetimes {
indices: SmallVec<[MaybeStatic<MethodLifetime>; 2]>,
}
impl LifetimeEnv {
/// Returns a new [`LifetimeEnv`].
pub(super) fn new(
nodes: SmallVec<[Lifetime; INLINE_NUM_LIFETIMES]>,
num_lifetimes: usize,
) -> Self {
Self {
nodes,
num_lifetimes,
}
}
/// Returns a fresh [`MethodLifetimes`] corresponding to `self`.
pub fn method_lifetimes(&self) -> MethodLifetimes {
let indices = (0..self.num_lifetimes)
.map(|index| MaybeStatic::NonStatic(MethodLifetime(index)))
.collect();
MethodLifetimes { 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(MethodLifetime),
{
SubtypeLifetimeVisitor::new(self, visit_fn)
}
}
impl TypeLifetime {
/// Returns a [`TypeLifetime`] 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(index)
}
pub(super) fn new(index: usize) -> Self {
Self(index)
}
/// Returns a new [`MaybeStatic<MethodLifetime>`] 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 `MethodLifetime` corresponding to `'b`.
pub fn as_method_lifetime(
self,
method_lifetimes: &MethodLifetimes,
) -> MaybeStatic<MethodLifetime> {
method_lifetimes.indices[self.0]
}
}
impl TypeLifetimes {
pub(super) fn from_fn<F>(lifetimes: &[ast::Lifetime], lower_fn: F) -> Self
where
F: FnMut(&ast::Lifetime) -> MaybeStatic<TypeLifetime>,
{
Self {
indices: lifetimes.iter().map(lower_fn).collect(),
}
}
/// Returns a new [`MethodLifetimes`] 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 [`TypeLifetimes`] containing `['a, 'b]`,
/// and `bar` will have a [`MethodLifetimes`] 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 [`MethodLifetimes`] 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: &MethodLifetimes) -> MethodLifetimes {
let indices = self
.indices
.iter()
.map(|maybe_static_lt| {
maybe_static_lt.flat_map_nonstatic(|lt| lt.as_method_lifetime(method_lifetimes))
})
.collect();
MethodLifetimes { indices }
}
}
impl MethodLifetime {
/// Returns a new `MethodLifetime` from an index into a `LifetimeEnv`.
pub(super) fn new(index: usize) -> Self {
Self(index)
}
}
impl MethodLifetimes {
/// Returns an iterator over the contained [`MethodLifetime`]s.
pub(super) fn lifetimes(&self) -> impl Iterator<Item = MaybeStatic<MethodLifetime>> + '_ {
self.indices.iter().copied()
}
}

View File

@ -0,0 +1,856 @@
use super::{
Borrow, EnumDef, EnumPath, EnumVariant, IdentBuf, LifetimeEnv, LifetimeLowerer, LookupId,
MaybeOwn, Method, NonOptional, OpaqueDef, OpaquePath, Optional, OutStructDef, OutStructField,
OutStructPath, OutType, Param, ParamLifetimeLowerer, ParamSelf, PrimitiveType,
ReturnLifetimeLowerer, ReturnType, ReturnableStructPath, SelfParamLifetimeLowerer, SelfType,
Slice, StructDef, StructField, StructPath, SuccessType, Type,
};
use crate::{ast, Env};
use core::fmt;
use strck_ident::IntoCk;
/// An error from lowering the AST to the HIR.
#[derive(Debug)]
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),
}
}
}
/// Lowers an [`ast::Ident`]s into an [`hir::IdentBuf`].
///
/// If there are any errors, they're pushed to `errors` and `None` is returned.
pub fn lower_ident(
ident: &ast::Ident,
context: &'static str,
errors: &mut Vec<LoweringError>,
) -> Option<IdentBuf> {
match ident.as_str().ck() {
Ok(name) => Some(name.to_owned()),
Err(e) => {
errors.push(LoweringError::Other(format!(
"Ident `{ident}` from {context} could not be turned into a Rust ident: {e}"
)));
None
}
}
}
/// Lowers an AST definition.
pub(super) trait TypeLowerer: Sized {
/// Type of the AST definition that gets lowered.
type AstDef;
/// Lowers the AST definition into `Self`.
///
/// If there are any errors, they're pushed to `errors` and `None` is returned.
fn lower(
ast_def: &Self::AstDef,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Self>;
/// Lowers multiple items at once
fn lower_all(
ast_defs: &[(&ast::Path, &Self::AstDef)],
lookup_id: &LookupId,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Vec<Self>> {
let mut hir_types = Some(Vec::with_capacity(ast_defs.len()));
for (in_path, ast_def) in ast_defs {
let hir_type = Self::lower(ast_def, lookup_id, in_path, env, errors);
match (hir_type, &mut hir_types) {
(Some(hir_type), Some(hir_types)) => hir_types.push(hir_type),
_ => hir_types = None,
}
}
hir_types
}
}
impl TypeLowerer for EnumDef {
type AstDef = ast::Enum;
fn lower(
ast_enum: &Self::AstDef,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Self> {
let name = lower_ident(&ast_enum.name, "enum name", errors);
let mut variants = Some(Vec::with_capacity(ast_enum.variants.len()));
for (ident, discriminant, docs) in ast_enum.variants.iter() {
let name = lower_ident(ident, "enum variant", errors);
match (name, &mut variants) {
(Some(name), Some(variants)) => {
variants.push(EnumVariant {
docs: docs.clone(),
name,
discriminant: *discriminant,
});
}
_ => variants = None,
}
}
let methods = lower_all_methods(&ast_enum.methods[..], lookup_id, in_path, env, errors);
Some(EnumDef::new(
ast_enum.docs.clone(),
name?,
variants?,
methods?,
))
}
}
impl TypeLowerer for OpaqueDef {
type AstDef = ast::OpaqueStruct;
fn lower(
ast_opaque: &Self::AstDef,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Self> {
let name = lower_ident(&ast_opaque.name, "opaque name", errors);
let methods = lower_all_methods(&ast_opaque.methods[..], lookup_id, in_path, env, errors);
Some(OpaqueDef::new(ast_opaque.docs.clone(), name?, methods?))
}
}
impl TypeLowerer for StructDef {
type AstDef = ast::Struct;
fn lower(
ast_struct: &Self::AstDef,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Self> {
let name = lower_ident(&ast_struct.name, "struct name", errors);
let fields = if ast_struct.fields.is_empty() {
errors.push(LoweringError::Other(format!(
"struct `{}` is a ZST because it has no fields",
ast_struct.name
)));
None
} else {
let mut fields = Some(Vec::with_capacity(ast_struct.fields.len()));
for (name, ty, docs) in ast_struct.fields.iter() {
let name = lower_ident(name, "struct field name", errors);
let ty = lower_type(
ty,
Some(&mut &ast_struct.lifetimes),
lookup_id,
in_path,
env,
errors,
);
match (name, ty, &mut fields) {
(Some(name), Some(ty), Some(fields)) => fields.push(StructField {
docs: docs.clone(),
name,
ty,
}),
_ => fields = None,
}
}
fields
};
let methods = lower_all_methods(&ast_struct.methods[..], lookup_id, in_path, env, errors);
Some(StructDef::new(
ast_struct.docs.clone(),
name?,
fields?,
methods?,
))
}
}
impl TypeLowerer for OutStructDef {
type AstDef = ast::Struct;
fn lower(
ast_out_struct: &Self::AstDef,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Self> {
let name = lower_ident(&ast_out_struct.name, "out-struct name", errors);
let fields = if ast_out_struct.fields.is_empty() {
errors.push(LoweringError::Other(format!(
"struct `{}` is a ZST because it has no fields",
ast_out_struct.name
)));
None
} else {
let mut fields = Some(Vec::with_capacity(ast_out_struct.fields.len()));
for (name, ty, docs) in ast_out_struct.fields.iter() {
let name = lower_ident(name, "out-struct field name", errors);
let ty = lower_out_type(
ty,
Some(&mut &ast_out_struct.lifetimes),
lookup_id,
in_path,
env,
errors,
);
match (name, ty, &mut fields) {
(Some(name), Some(ty), Some(fields)) => fields.push(OutStructField {
docs: docs.clone(),
name,
ty,
}),
_ => fields = None,
}
}
fields
};
let methods =
lower_all_methods(&ast_out_struct.methods[..], lookup_id, in_path, env, errors);
Some(OutStructDef::new(
ast_out_struct.docs.clone(),
name?,
fields?,
methods?,
))
}
}
/// 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(
method: &ast::Method,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Method> {
let name = lower_ident(&method.name, "method name", errors);
let (ast_params, takes_writeable) = match method.params.split_last() {
Some((last, remaining)) if last.is_writeable() => (remaining, true),
_ => (&method.params[..], false),
};
let self_param_ltl = SelfParamLifetimeLowerer::new(&method.lifetime_env, errors);
let (param_self, param_ltl) = if let Some(self_param) = method.self_param.as_ref() {
lower_self_param(
self_param,
self_param_ltl,
lookup_id,
&method.full_path_name,
in_path,
env,
errors,
)
.map(|(param_self, param_ltl)| (Some(Some(param_self)), Some(param_ltl)))
.unwrap_or((None, None))
} else {
(
Some(None),
self_param_ltl.map(SelfParamLifetimeLowerer::no_self_ref),
)
};
let (params, return_ltl) =
lower_many_params(ast_params, param_ltl, lookup_id, in_path, env, errors)
.map(|(params, return_ltl)| (Some(params), Some(return_ltl)))
.unwrap_or((None, None));
let (output, lifetime_env) = lower_return_type(
method.return_type.as_ref(),
takes_writeable,
return_ltl,
lookup_id,
in_path,
env,
errors,
)?;
Some(Method {
docs: method.docs.clone(),
name: name?,
lifetime_env,
param_self: param_self?,
params: params?,
output,
})
}
/// 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(
ast_methods: &[ast::Method],
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Vec<Method>> {
let mut methods = Some(Vec::with_capacity(ast_methods.len()));
for method in ast_methods {
let method = lower_method(method, lookup_id, in_path, env, errors);
match (method, &mut methods) {
(Some(method), Some(methods)) => {
methods.push(method);
}
_ => methods = None,
}
}
methods
}
/// Lowers an [`ast::TypeName`]s into a [`hir::Type`].
///
/// If there are any errors, they're pushed to `errors` and `None` is returned.
fn lower_type<L: LifetimeLowerer>(
ty: &ast::TypeName,
ltl: Option<&mut L>,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Type> {
match ty {
ast::TypeName::Primitive(prim) => Some(Type::Primitive(PrimitiveType::from_ast(*prim))),
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
match path.resolve(in_path, env) {
ast::CustomType::Struct(strct) => {
if let Some(tcx_id) = lookup_id.resolve_struct(strct) {
let lifetimes = ltl?.lower_generics(&path.lifetimes[..], ty.is_self());
Some(Type::Struct(StructPath::new(lifetimes, tcx_id)))
} else if lookup_id.resolve_out_struct(strct).is_some() {
errors.push(LoweringError::Other(format!("found struct in input that is marked with #[diplomat::out]: {ty} in {path}")));
None
} else {
unreachable!("struct `{}` wasn't found in the set of structs or out-structs, this is a bug.", strct.name);
}
}
ast::CustomType::Opaque(_) => {
errors.push(LoweringError::Other(format!(
"Opaque passed by value in input: {path}"
)));
None
}
ast::CustomType::Enum(enm) => {
let tcx_id = lookup_id
.resolve_enum(enm)
.expect("can't find enum in lookup map, which contains all enums from env");
Some(Type::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, env) {
ast::CustomType::Opaque(opaque) => ltl.map(|ltl| {
let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
let lifetimes = ltl.lower_generics(&path.lifetimes[..], ref_ty.is_self());
let tcx_id = lookup_id.resolve_opaque(opaque).expect(
"can't find opaque in lookup map, which contains all opaques from env",
);
Type::Opaque(OpaquePath::new(lifetimes, Optional(false), borrow, tcx_id))
}),
_ => {
errors.push(LoweringError::Other(format!("found &T in input where T is a custom type, but not opaque. T = {ref_ty}")));
None
}
}
}
_ => {
errors.push(LoweringError::Other(format!("found &T in input where T isn't a custom type and therefore not opaque. T = {ref_ty}")));
None
}
},
ast::TypeName::Box(box_ty) => {
errors.push(match box_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
match path.resolve(in_path, 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}")),
});
None
}
ast::TypeName::Option(opt_ty) => {
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, env) {
ast::CustomType::Opaque(opaque) => ltl.map(|ltl| {
let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
let lifetimes = ltl.lower_generics(&path.lifetimes, ref_ty.is_self());
let tcx_id = lookup_id.resolve_opaque(opaque).expect(
"can't find opaque in lookup map, which contains all opaques from env",
);
Type::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
borrow,
tcx_id,
))
}),
_ => {
errors.push(LoweringError::Other(format!("found Option<&T> in input where T is a custom type, but it's not opaque. T = {ref_ty}")));
None
}
},
_ => {
errors.push(LoweringError::Other(format!("found Option<&T> in input, but T isn't a custom type and therefore not opaque. T = {ref_ty}")));
None
}
},
ast::TypeName::Box(box_ty) => {
// we could see whats in the box here too
errors.push(LoweringError::Other(format!("found Option<Box<T>> in input, but box isn't allowed in inputs. T = {box_ty}")));
None
}
_ => {
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}")));
None
}
}
}
ast::TypeName::Result(_, _, _) => {
errors.push(LoweringError::Other(
"Results can only appear as the top-level return type of methods".into(),
));
None
}
ast::TypeName::Writeable => {
errors.push(LoweringError::Other(
"Writeables can only appear as the last parameter of a method".into(),
));
None
}
ast::TypeName::StrReference(lifetime) => {
Some(Type::Slice(Slice::Str(ltl?.lower_lifetime(lifetime))))
}
ast::TypeName::PrimitiveSlice(lifetime, mutability, prim) => {
let borrow = Borrow::new(ltl?.lower_lifetime(lifetime), *mutability);
let prim = PrimitiveType::from_ast(*prim);
Some(Type::Slice(Slice::Primitive(borrow, prim)))
}
ast::TypeName::Unit => {
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()));
None
}
}
}
/// 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<L: LifetimeLowerer>(
ty: &ast::TypeName,
ltl: Option<&mut L>,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<OutType> {
match ty {
ast::TypeName::Primitive(prim) => Some(OutType::Primitive(PrimitiveType::from_ast(*prim))),
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
match path.resolve(in_path, env) {
ast::CustomType::Struct(strct) => {
let lifetimes = ltl?.lower_generics(&path.lifetimes, ty.is_self());
if let Some(tcx_id) = lookup_id.resolve_struct(strct) {
Some(OutType::Struct(ReturnableStructPath::Struct(
StructPath::new(lifetimes, tcx_id),
)))
} else if let Some(tcx_id) = lookup_id.resolve_out_struct(strct) {
Some(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(_) => {
errors.push(LoweringError::Other(format!(
"Opaque passed by value in input: {path}"
)));
None
}
ast::CustomType::Enum(enm) => {
let tcx_id = lookup_id
.resolve_enum(enm)
.expect("can't find enum in lookup map, which contains all enums from env");
Some(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, env) {
ast::CustomType::Opaque(opaque) => ltl.map(|ltl| {
let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
let lifetimes = ltl.lower_generics(&path.lifetimes, ref_ty.is_self());
let tcx_id = lookup_id.resolve_opaque(opaque).expect(
"can't find opaque in lookup map, which contains all opaques from env",
);
OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(false),
MaybeOwn::Borrow(borrow),
tcx_id,
))
}),
_ => {
errors.push(LoweringError::Other(format!("found &T in output where T is a custom type, but not opaque. T = {ref_ty}")));
None
}
}
}
_ => {
errors.push(LoweringError::Other(format!("found &T in output where T isn't a custom type and therefore not opaque. T = {ref_ty}")));
None
}
},
ast::TypeName::Box(box_ty) => match box_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
match path.resolve(in_path, env) {
ast::CustomType::Opaque(opaque) => ltl.map(|ltl| {
let lifetimes = ltl.lower_generics(&path.lifetimes, box_ty.is_self());
let tcx_id = lookup_id.resolve_opaque(opaque).expect(
"can't find opaque in lookup map, which contains all opaques from env",
);
OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
MaybeOwn::Own,
tcx_id,
))
}),
_ => {
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}")));
None
}
}
}
_ => {
errors.push(LoweringError::Other(format!(
"found Box<T> in output where T isn't a custom type. T = {box_ty}"
)));
None
}
},
ast::TypeName::Option(opt_ty) => 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, env) {
ast::CustomType::Opaque(opaque) => ltl.map(|ltl| {
let borrow = Borrow::new(ltl.lower_lifetime(lifetime), *mutability);
let lifetimes = ltl.lower_generics(&path.lifetimes, ref_ty.is_self());
let tcx_id = lookup_id.resolve_opaque(opaque).expect(
"can't find opaque in lookup map, which contains all opaques from env",
);
OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
MaybeOwn::Borrow(borrow),
tcx_id,
))
}),
_ => {
errors.push(LoweringError::Other(format!("found Option<&T> where T is a custom type, but it's not opaque. T = {ref_ty}")));
None
}
}
}
_ => {
errors.push(LoweringError::Other(format!("found Option<&T>, but T isn't a custom type and therefore not opaque. T = {ref_ty}")));
None
}
},
ast::TypeName::Box(box_ty) => match box_ty.as_ref() {
ast::TypeName::Named(path) | ast::TypeName::SelfType(path) => {
match path.resolve(in_path, env) {
ast::CustomType::Opaque(opaque) => {
let lifetimes = ltl?.lower_generics(&path.lifetimes, box_ty.is_self());
let tcx_id = lookup_id.resolve_opaque(opaque).expect(
"can't find opaque in lookup map, which contains all opaques from env",
);
Some(OutType::Opaque(OpaquePath::new(
lifetimes,
Optional(true),
MaybeOwn::Own,
tcx_id,
)))
}
_ => {
errors.push(LoweringError::Other(format!("found Option<Box<T>> where T is a custom type, but it's not opaque. T = {box_ty}")));
None
}
}
}
_ => {
errors.push(LoweringError::Other(format!("found Option<Box<T>>, but T isn't a custom type and therefore not opaque. T = {box_ty}")));
None
}
},
_ => {
errors.push(LoweringError::Other(format!("found Option<T>, where T isn't a reference but Option<T> in inputs requires that T is a reference to an opaque. T = {opt_ty}")));
None
}
},
ast::TypeName::Result(_, _, _) => {
errors.push(LoweringError::Other(
"Results can only appear as the top-level return type of methods".into(),
));
None
}
ast::TypeName::Writeable => {
errors.push(LoweringError::Other(
"Writeables can only appear as the last parameter of a method".into(),
));
None
}
ast::TypeName::StrReference(lifetime) => {
Some(OutType::Slice(Slice::Str(ltl?.lower_lifetime(lifetime))))
}
ast::TypeName::PrimitiveSlice(lifetime, mutability, prim) => {
let borrow = Borrow::new(ltl?.lower_lifetime(lifetime), *mutability);
let prim = PrimitiveType::from_ast(*prim);
Some(OutType::Slice(Slice::Primitive(borrow, prim)))
}
ast::TypeName::Unit => {
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()));
None
}
}
}
/// 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<'ast>(
self_param: &ast::SelfParam,
self_param_ltl: Option<SelfParamLifetimeLowerer<'ast>>,
lookup_id: &LookupId,
method_full_path: &ast::Ident, // for better error msg
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<(ParamSelf, ParamLifetimeLowerer<'ast>)> {
match self_param.path_type.resolve(in_path, env) {
ast::CustomType::Struct(strct) => {
if let Some(tcx_id) = lookup_id.resolve_struct(strct) {
if self_param.reference.is_some() {
errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes a reference to a struct as a self parameter, which isn't allowed")));
None
} 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[..], true);
Some((
ParamSelf::new(SelfType::Struct(StructPath::new(type_lifetimes, tcx_id))),
param_ltl,
))
}
} else if lookup_id.resolve_out_struct(strct).is_some() {
if let Some((lifetime, _)) = &self_param.reference {
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")));
None
} else {
errors.push(LoweringError::Other(format!("Method `{method_full_path}` takes an out-struct as the self parameter, which isn't allowed")));
None
}
} 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 = 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, true);
Some((
ParamSelf::new(SelfType::Opaque(OpaquePath::new(
lifetimes,
NonOptional,
borrow,
tcx_id,
))),
param_ltl,
))
} else {
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")));
None
}
}
ast::CustomType::Enum(enm) => {
let tcx_id = lookup_id.resolve_enum(enm).expect("enum is in env");
Some((
ParamSelf::new(SelfType::Enum(EnumPath::new(tcx_id))),
self_param_ltl?.no_self_ref(),
))
}
}
}
/// 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 writeable param at the end in
/// the method, it's not passed into here.
fn lower_param<L: LifetimeLowerer>(
param: &ast::Param,
ltl: Option<&mut L>,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<Param> {
let name = lower_ident(&param.name, "param name", errors);
let ty = lower_type(&param.ty, ltl, lookup_id, in_path, env, errors);
Some(Param::new(name?, ty?))
}
/// 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 writeable param at the end in
/// the method, `ast_params` was sliced to not include it. This happens in
/// `lower_method`, the caller of this function.
fn lower_many_params<'ast>(
ast_params: &[ast::Param],
mut param_ltl: Option<ParamLifetimeLowerer<'ast>>,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<(Vec<Param>, ReturnLifetimeLowerer<'ast>)> {
let mut params = Some(Vec::with_capacity(ast_params.len()));
for param in ast_params {
let param = lower_param(param, param_ltl.as_mut(), lookup_id, in_path, env, errors);
match (param, &mut params) {
(Some(param), Some(params)) => {
params.push(param);
}
_ => params = None,
}
}
Some((params?, param_ltl?.into_return_ltl()))
}
/// 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(
return_type: Option<&ast::TypeName>,
takes_writeable: bool,
mut return_ltl: Option<ReturnLifetimeLowerer<'_>>,
lookup_id: &LookupId,
in_path: &ast::Path,
env: &Env,
errors: &mut Vec<LoweringError>,
) -> Option<(ReturnType, LifetimeEnv)> {
let writeable_option = if takes_writeable {
Some(SuccessType::Writeable)
} else {
None
};
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 => Some(writeable_option),
ty => lower_out_type(ty, return_ltl.as_mut(), lookup_id, in_path, env, errors)
.map(|ty| Some(SuccessType::OutType(ty))),
};
let err_ty = match err_ty.as_ref() {
ast::TypeName::Unit => Some(None),
ty => lower_out_type(ty, return_ltl.as_mut(), lookup_id, in_path, env, errors)
.map(Some),
};
match (ok_ty, err_ty) {
(Some(ok_ty), Some(err_ty)) => Some(ReturnType::Fallible(ok_ty, err_ty)),
_ => None,
}
}
ast::TypeName::Unit => Some(ReturnType::Infallible(writeable_option)),
ty => lower_out_type(ty, return_ltl.as_mut(), lookup_id, in_path, env, errors)
.map(|ty| ReturnType::Infallible(Some(SuccessType::OutType(ty)))),
}
.and_then(|return_fallability| Some((return_fallability, return_ltl?.finish())))
}

View File

@ -0,0 +1,489 @@
//! Methods for types and navigating lifetimes within methods.
use std::fmt::{self, Write};
use smallvec::SmallVec;
use super::{
paths, Docs, Ident, IdentBuf, LifetimeEnv, MaybeStatic, MethodLifetime, MethodLifetimes,
OutType, SelfType, Slice, Type, TypeContext, TypeLifetime, TypeLifetimes,
};
/// A method exposed to Diplomat.
#[derive(Debug)]
pub struct Method {
pub docs: Docs,
pub name: IdentBuf,
pub lifetime_env: LifetimeEnv,
pub param_self: Option<ParamSelf>,
pub params: Vec<Param>,
pub output: ReturnType,
}
/// Type that the method returns.
#[derive(Debug)]
pub enum SuccessType {
Writeable,
OutType(OutType),
}
/// Whether or not the method returns a value or a result.
#[derive(Debug)]
pub enum ReturnType {
Infallible(Option<SuccessType>),
Fallible(Option<SuccessType>, Option<OutType>),
}
/// The `self` parameter of a method.
#[derive(Debug)]
pub struct ParamSelf {
pub ty: SelfType,
}
/// A parameter in a method.
#[derive(Debug)]
pub struct Param {
pub name: IdentBuf,
pub ty: Type,
}
/// An id for indexing into a [`BorrowingFieldsVisitor`].
#[derive(Copy, Clone, Debug)]
struct ParentId(usize);
/// 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.
pub struct BorrowingFieldVisitor<'m> {
parents: SmallVec<[(Option<ParentId>, &'m Ident); INLINE_NUM_PARENTS]>,
leaves: SmallVec<[BorrowingFieldVisitorLeaf; INLINE_NUM_LEAVES]>,
}
/// Non-recursive input-output types that contain lifetimes
enum BorrowingFieldVisitorLeaf {
Opaque(ParentId, MaybeStatic<MethodLifetime>, MethodLifetimes),
Slice(ParentId, MaybeStatic<MethodLifetime>),
}
/// 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,
}
impl SuccessType {
/// Returns `true` if it's writeable, otherwise `false`.
pub fn is_writeable(&self) -> bool {
matches!(self, SuccessType::Writeable)
}
/// Returns a return type, if it's not a writeable.
pub fn as_type(&self) -> Option<&OutType> {
match self {
SuccessType::Writeable => None,
SuccessType::OutType(ty) => Some(ty),
}
}
}
impl ReturnType {
/// Returns `true` if it's writeable, otherwise `false`.
pub fn is_writeable(&self) -> bool {
self.return_type()
.map(SuccessType::is_writeable)
.unwrap_or(false)
}
/// Returns the [`ReturnOk`] value, whether it's the single return type or
/// the `Ok` variant of a result.
pub fn return_type(&self) -> Option<&SuccessType> {
match self {
ReturnType::Infallible(ret) | ReturnType::Fallible(ret, _) => ret.as_ref(),
}
}
/// Returns `true` if the FFI function returns a value (such that it may be assigned to a variable).
pub fn returns_value(&self) -> bool {
match self {
ReturnType::Fallible(_, _) => true,
ReturnType::Infallible(Some(SuccessType::OutType(_))) => true,
ReturnType::Infallible(Some(SuccessType::Writeable)) => false,
ReturnType::Infallible(None) => false,
}
}
}
impl ParamSelf {
pub(super) fn new(ty: SelfType) -> Self {
Self { ty }
}
/// 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 Param {
pub(super) fn new(name: IdentBuf, ty: Type) -> Self {
Self { name, ty }
}
}
impl Method {
/// Returns `true` if the method takes a writeable as an out parameter,
/// otherwise `false`.
pub fn is_writeable(&self) -> bool {
self.output.is_writeable()
}
/// Returns a fresh [`MethodLifetimes`] corresponding to `self`.
pub fn method_lifetimes(&self) -> MethodLifetimes {
self.lifetime_env.method_lifetimes()
}
/// Returns a new [`BorrowingFieldVisitor`], which allocates memory to
/// efficiently represent all fields (and their paths!) of the inputs that
/// have a lifetime.
/// ```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)
}
}
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
}
}
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
/// `MethodLifetime`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<MethodLifetime>, 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.
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 &param_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(
&param.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(
ty: &'m Type,
tcx: &'m TypeContext,
parent: ParentId,
method_lifetimes: &MethodLifetimes,
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 TypeLifetimes,
borrow: &'m MaybeStatic<TypeLifetime>,
parent: ParentId,
method_lifetimes: &MethodLifetimes,
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: &MethodLifetimes,
leaves: &mut SmallVec<[BorrowingFieldVisitorLeaf; 8]>,
) {
let method_lifetime = slice
.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: &MethodLifetimes,
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())
})
}
}

View File

@ -0,0 +1,29 @@
//! Experimental high-level representation (HIR) for Diplomat.
//!
//! Enabled with the `"hir"` Cargo feature
mod defs;
mod elision;
mod lifetimes;
mod lowering;
mod methods;
mod paths;
mod primitives;
mod ty_position;
mod type_context;
mod types;
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::LoweringError;
pub use crate::ast::Docs;
pub use strck_ident::rust::{Ident, IdentBuf};

View File

@ -0,0 +1,167 @@
use super::{
Borrow, EnumDef, EnumId, Everywhere, OpaqueDef, OpaqueId, OpaqueOwner, OutStructDef,
OutputOnly, ReturnableStructDef, StructDef, TyPosition, TypeContext, TypeLifetimes,
};
/// Path to a struct that may appear as an output.
#[derive(Debug, Clone)]
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)]
pub struct StructPath<P: TyPosition = Everywhere> {
pub lifetimes: TypeLifetimes,
pub tcx_id: P::StructId,
}
/// 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)]
pub struct OpaquePath<Opt, Owner> {
pub lifetimes: TypeLifetimes,
pub optional: Opt,
pub owner: Owner,
pub tcx_id: OpaqueId,
}
#[derive(Debug, Copy, Clone)]
pub struct Optional(pub(super) bool);
#[derive(Debug, Copy, Clone)]
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)]
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)]
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))
}
}
}
}
impl<P: TyPosition> StructPath<P> {
/// Returns a new [`EnumPath`].
pub(super) fn new(lifetimes: TypeLifetimes, 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)
}
}
impl<Opt, Owner> OpaquePath<Opt, Owner> {
/// Returns a new [`EnumPath`].
pub(super) fn new(
lifetimes: TypeLifetimes,
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 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)
}
}

View File

@ -0,0 +1,128 @@
//! 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)]
pub enum IntType {
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
}
/// Platform-dependent signed and unsigned size types.
#[derive(Copy, Clone, Debug)]
pub enum IntSizeType {
Isize,
Usize,
}
/// 128-bit signed and unsigned integers.
#[derive(Copy, Clone, Debug)]
pub enum Int128Type {
I128,
U128,
}
/// 32 and 64-bit floating point numbers.
#[derive(Copy, Clone, Debug)]
pub enum FloatType {
F32,
F64,
}
/// All primitive types.
#[derive(Copy, Clone, Debug)]
pub enum PrimitiveType {
Bool,
Char,
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,
}
}
/// Returns the string representation of `self`.
pub fn as_str(&self) -> &'static str {
match self {
PrimitiveType::Bool => "bool",
PrimitiveType::Char => "char",
PrimitiveType::Int(ty) => ty.as_str(),
PrimitiveType::IntSize(ty) => ty.as_str(),
PrimitiveType::Int128(ty) => ty.as_str(),
PrimitiveType::Float(ty) => ty.as_str(),
}
}
}

View File

@ -0,0 +1,25 @@
---
source: core/src/hir/elision.rs
expression: map
---
{
Static: [
"this.name",
"_s",
],
NonStatic(
MethodLifetime(
0,
),
): [
"this.p_data",
],
NonStatic(
MethodLifetime(
1,
),
): [
"this.q_data",
"this.inner.more_data",
],
}

View File

@ -0,0 +1,208 @@
---
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: TypeLifetimes {
indices: [
NonStatic(
TypeLifetime(
0,
),
),
],
},
optional: Optional(
true,
),
owner: Own,
tcx_id: OpaqueId(
0,
),
},
),
},
],
methods: [
Method {
docs: Docs(
"",
[],
),
name: "new",
lifetime_env: LifetimeEnv {
nodes: [
Lifetime {
ident: "a",
longer: [],
shorter: [],
},
],
num_lifetimes: 1,
},
param_self: None,
params: [
Param {
name: "s",
ty: Slice(
Str(
NonStatic(
TypeLifetime(
0,
),
),
),
),
},
],
output: Infallible(
Some(
OutType(
Struct(
OutStruct(
StructPath {
lifetimes: TypeLifetimes {
indices: [
NonStatic(
TypeLifetime(
0,
),
),
],
},
tcx_id: OutStructId(
0,
),
},
),
),
),
),
),
},
],
},
],
structs: [
StructDef {
docs: Docs(
"",
[],
),
name: "Struct",
fields: [
StructField {
docs: Docs(
"",
[],
),
name: "s",
ty: Slice(
Str(
NonStatic(
TypeLifetime(
0,
),
),
),
),
},
],
methods: [
Method {
docs: Docs(
"",
[],
),
name: "rustc_elision",
lifetime_env: LifetimeEnv {
nodes: [
Lifetime {
ident: "a",
longer: [],
shorter: [],
},
],
num_lifetimes: 2,
},
param_self: Some(
ParamSelf {
ty: Struct(
StructPath {
lifetimes: TypeLifetimes {
indices: [
NonStatic(
TypeLifetime(
0,
),
),
],
},
tcx_id: StructId(
0,
),
},
),
},
),
params: [
Param {
name: "s",
ty: Slice(
Str(
NonStatic(
TypeLifetime(
1,
),
),
),
),
},
],
output: Infallible(
Some(
OutType(
Slice(
Str(
NonStatic(
TypeLifetime(
1,
),
),
),
),
),
),
),
},
],
},
],
opaques: [
OpaqueDef {
docs: Docs(
"",
[],
),
name: "Opaque",
methods: [],
},
],
enums: [],
}

View File

@ -0,0 +1,182 @@
use super::{
Borrow, MaybeOwn, Mutability, OutStructId, ReturnableStructPath, StructId, StructPath, 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 {
const IS_OUT_ONLY: bool;
/// 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;
fn id_for_path(p: &Self::StructPath) -> TypeId;
}
/// One of two types implementing [`TyPosition`], representing types that can be
/// used as both input and output to functions.
///
/// The complement of this type is [`OutputOnly`].
#[derive(Debug, Copy, Clone)]
pub struct Everywhere;
/// One of two types implementing [`TyPosition`], representing types that can
/// only be used as return types in functions.
///
/// The complement of this type is [`Everywhere`].
#[derive(Debug, Copy, Clone)]
pub struct OutputOnly;
impl TyPosition for Everywhere {
const IS_OUT_ONLY: bool = false;
type OpaqueOwnership = Borrow;
type StructId = StructId;
type StructPath = StructPath;
fn id_for_path(p: &Self::StructPath) -> TypeId {
p.tcx_id.into()
}
}
impl TyPosition for OutputOnly {
const IS_OUT_ONLY: bool = true;
type OpaqueOwnership = MaybeOwn;
type StructId = OutStructId;
type StructPath = ReturnableStructPath;
fn id_for_path(p: &Self::StructPath) -> TypeId {
match p {
ReturnableStructPath::Struct(p) => p.tcx_id.into(),
ReturnableStructPath::OutStruct(p) => p.tcx_id.into(),
}
}
}
/// 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;
}
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,
}
}
}
impl OpaqueOwner for Borrow {
fn mutability(&self) -> Option<Mutability> {
Some(self.mutability)
}
fn is_owned(&self) -> bool {
false
}
}

Some files were not shown because too many files have changed in this diff Show More