diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /third_party/rust/uniffi | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
249 files changed, 8633 insertions, 4969 deletions
diff --git a/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json b/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json index 24e445a094..33a5447821 100644 --- a/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json +++ b/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"056d32277bd81040868270ae29d54b3d59ba08833cc307e8aee14a61b914d658","build.rs":"7d98b49c1d9c868c4199f0417eaa017ab459cdd536e9a29851d5f707941f9ead","src/arithmetic.udl":"8554c6907ece627645f6b896f71430e5412bf19b0ac6becf63eb9a69868d0f7a","src/lib.rs":"c454193443e92d49f997c760f4131192fb66bf213bbac1710c1ebde19e765e5d","tests/bindings/test_arithmetic.kts":"e0e9347755db4e18f70b1b74c2d5a4aa328373015090ed959b46d65c2a205d92","tests/bindings/test_arithmetic.py":"3e41d69e21e96a6830197c760f3b7bddd754edc0c5515b7bd33b79cccb10f941","tests/bindings/test_arithmetic.rb":"ea0fdce0a4c7b557b427db77521da05240cd6e87d60a128ac2307fab3bbbc76d","tests/bindings/test_arithmetic.swift":"455b87d95fc690af9c35f9e43676e9c855dedddd2fc1c9e1cbaa6a02835c2d4c","tests/test_generated_bindings.rs":"26b92d6b3e648f6fadd4182cbdba4f412b73da48a789785fd98cd486b29abf05","uniffi.toml":"a2d4f46fa51dc1be1e8bcdf67ec13223637fc1b6c6437455cf53c2dae065fb45"},"package":null}
\ No newline at end of file +{"files":{"Cargo.toml":"359cffb76e0eac82aeec6a667f7670fa4b88346c2dd7c17febe71731fd6df58b","build.rs":"7d98b49c1d9c868c4199f0417eaa017ab459cdd536e9a29851d5f707941f9ead","src/arithmetic.udl":"8554c6907ece627645f6b896f71430e5412bf19b0ac6becf63eb9a69868d0f7a","src/lib.rs":"c454193443e92d49f997c760f4131192fb66bf213bbac1710c1ebde19e765e5d","tests/bindings/test_arithmetic.kts":"e0e9347755db4e18f70b1b74c2d5a4aa328373015090ed959b46d65c2a205d92","tests/bindings/test_arithmetic.py":"3e41d69e21e96a6830197c760f3b7bddd754edc0c5515b7bd33b79cccb10f941","tests/bindings/test_arithmetic.rb":"ea0fdce0a4c7b557b427db77521da05240cd6e87d60a128ac2307fab3bbbc76d","tests/bindings/test_arithmetic.swift":"455b87d95fc690af9c35f9e43676e9c855dedddd2fc1c9e1cbaa6a02835c2d4c","tests/test_generated_bindings.rs":"26b92d6b3e648f6fadd4182cbdba4f412b73da48a789785fd98cd486b29abf05","uniffi.toml":"a2d4f46fa51dc1be1e8bcdf67ec13223637fc1b6c6437455cf53c2dae065fb45"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-arithmetic/Cargo.toml b/third_party/rust/uniffi-example-arithmetic/Cargo.toml index 4efddceb0a..52f08bb9f6 100644 --- a/third_party/rust/uniffi-example-arithmetic/Cargo.toml +++ b/third_party/rust/uniffi-example-arithmetic/Cargo.toml @@ -28,15 +28,15 @@ crate-type = [ thiserror = "1.0" [dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" [dev-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["bindgen-tests"] [build-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["build"] diff --git a/third_party/rust/uniffi-example-geometry/.cargo-checksum.json b/third_party/rust/uniffi-example-geometry/.cargo-checksum.json index 43ff278460..52b8ef33db 100644 --- a/third_party/rust/uniffi-example-geometry/.cargo-checksum.json +++ b/third_party/rust/uniffi-example-geometry/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"caa1fbdbfdf87670cf571dda2e693e8bc9ba38a0a8fbf803014664e84cde3213","build.rs":"fc5da645c8862e15f3b6879db179a1e5eec6161dc1cfbf95a4db9daf107a133f","src/geometry.udl":"7da7a6ec080c7117ec3c25206e23f9ed436e60b1a26fba34f991547680443550","src/lib.rs":"f9d004c97efb1a719368169f0aab181f27439eda3520c1afaca2420433226682","tests/bindings/test_geometry.kts":"e537185e3c699df1c0468525700e8a38f9a504b2a663c38442146b951e38e9a7","tests/bindings/test_geometry.py":"3ea483b8a4fbe13aefa6641177ae149f75f734bc32bf0da533b97c1abf3dc317","tests/bindings/test_geometry.rb":"17c2fe8a7b477419a6646983dd88f1b07a0304b58a568c03e9bfa640d5b2df5c","tests/bindings/test_geometry.swift":"a61fec6bfe16020809e20e4da372748c24366767138c5672a0bfff85c4b62d78","tests/test_generated_bindings.rs":"ff8fc093ccb6ee3ee2235c09276c7bb87234ad143667429cb721e46379577f3d"},"package":null}
\ No newline at end of file +{"files":{"Cargo.toml":"2d2c1ec66f7e2aaa4553b5338e68a7b64d68b84152547e1ccc4eeb019ca97103","build.rs":"fc5da645c8862e15f3b6879db179a1e5eec6161dc1cfbf95a4db9daf107a133f","src/geometry.udl":"7da7a6ec080c7117ec3c25206e23f9ed436e60b1a26fba34f991547680443550","src/lib.rs":"f9d004c97efb1a719368169f0aab181f27439eda3520c1afaca2420433226682","tests/bindings/test_geometry.kts":"e537185e3c699df1c0468525700e8a38f9a504b2a663c38442146b951e38e9a7","tests/bindings/test_geometry.py":"600e74ba0eba4e35d824c8f6d5bd5a2120a470017e8465c32d1e954a1939d323","tests/bindings/test_geometry.rb":"651de70af595f8b52ef030a03356939e8c1d0b40e44b4155b45d565d1d1dcbed","tests/bindings/test_geometry.swift":"a61fec6bfe16020809e20e4da372748c24366767138c5672a0bfff85c4b62d78","tests/test_generated_bindings.rs":"ff8fc093ccb6ee3ee2235c09276c7bb87234ad143667429cb721e46379577f3d"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-geometry/Cargo.toml b/third_party/rust/uniffi-example-geometry/Cargo.toml index b9fe377732..b9c8a0beb6 100644 --- a/third_party/rust/uniffi-example-geometry/Cargo.toml +++ b/third_party/rust/uniffi-example-geometry/Cargo.toml @@ -25,15 +25,15 @@ crate-type = [ ] [dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" [dev-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["bindgen-tests"] [build-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["build"] diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py index a40e2af6ec..fd6772be24 100644 --- a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py +++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py @@ -1,10 +1,10 @@ from geometry import * -ln1 = Line(Point(0,0), Point(1,2)) -ln2 = Line(Point(1,1), Point(2,2)) +ln1 = Line(start=Point(coord_x=0, coord_y=0), end=Point(coord_x=1, coord_y=2)) +ln2 = Line(start=Point(coord_x=1, coord_y=1), end=Point(coord_x=2, coord_y=2)) assert gradient(ln1) == 2 assert gradient(ln2) == 1 -assert intersection(ln1, ln2) == Point(0, 0) +assert intersection(ln1, ln2) == Point(coord_x=0, coord_y=0) assert intersection(ln1, ln1) is None diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb index 8b1280d823..90fdff684e 100644 --- a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb +++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb @@ -6,11 +6,11 @@ require 'geometry' include Test::Unit::Assertions include Geometry -ln1 = Line.new(Point.new(0.0, 0.0), Point.new(1.0, 2.0)) -ln2 = Line.new(Point.new(1.0, 1.0), Point.new(2.0, 2.0)) +ln1 = Line.new(start: Point.new(coord_x: 0.0, coord_y: 0.0), _end: Point.new(coord_x: 1.0, coord_y: 2.0)) +ln2 = Line.new(start: Point.new(coord_x: 1.0, coord_y: 1.0), _end: Point.new(coord_x: 2.0, coord_y: 2.0)) assert_equal Geometry.gradient(ln1), 2 assert_equal Geometry.gradient(ln2), 1 -assert_equal Geometry.intersection(ln1, ln2), Point.new(0, 0) +assert_equal Geometry.intersection(ln1, ln2), Point.new(coord_x: 0, coord_y: 0) assert Geometry.intersection(ln1, ln1).nil? diff --git a/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json b/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json index 87e0d96e40..2f1f261a13 100644 --- a/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json +++ b/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"e945bedc4897a588199714e71d6210a77d051925af39b345e605fc58fb782b50","build.rs":"ba88cce38ecd3321a7a93623755e3339e255360a7f946d3779ded804662c081a","src/lib.rs":"740d70ab5ca22eefcc291a56a9e4ed84e9669f4cfe3890e7d79bc56ae4b991a3","src/rondpoint.udl":"c903cb8c95b3ec1b103350857c3c3bc428bfd90c86a6c48089db9e0fc6e41eb5","tests/bindings/test_rondpoint.kts":"4aac8353278807f4add95c81f4c6c61187204b9767f882fd64872ed8ac1f6451","tests/bindings/test_rondpoint.py":"d618274170af767f8a5614a2565ea698b26ea3e1a222d5c110e7b2d00763e73b","tests/bindings/test_rondpoint.rb":"9cc49df311823d6caedbe7b05ff8c4da6329063c2ce16810192aaaa7edcdf5f5","tests/bindings/test_rondpoint.swift":"fa806e7e09c22ed44496658f6e0781765447bbdd250d7adf4b1152248ed70e69","tests/test_generated_bindings.rs":"5464f89e91c458f164b83a454c6df67a2953873e8a785a4720a2253d843f88e5"},"package":null}
\ No newline at end of file +{"files":{"Cargo.toml":"c0706abf631a178bfbc2e53b0b714d4a7b73059c46e1326efa4f52d235dc05f5","build.rs":"ba88cce38ecd3321a7a93623755e3339e255360a7f946d3779ded804662c081a","src/lib.rs":"740d70ab5ca22eefcc291a56a9e4ed84e9669f4cfe3890e7d79bc56ae4b991a3","src/rondpoint.udl":"c903cb8c95b3ec1b103350857c3c3bc428bfd90c86a6c48089db9e0fc6e41eb5","tests/bindings/test_rondpoint.kts":"4aac8353278807f4add95c81f4c6c61187204b9767f882fd64872ed8ac1f6451","tests/bindings/test_rondpoint.py":"af25a56c35da9a934fb9f098c25f57329c53d461be378e4c5089b12a45efa28b","tests/bindings/test_rondpoint.rb":"d4b4523084534266ea7ef3161021b9903cb8d7a75cf4624c59055af9f02d22f9","tests/bindings/test_rondpoint.swift":"fa806e7e09c22ed44496658f6e0781765447bbdd250d7adf4b1152248ed70e69","tests/test_generated_bindings.rs":"5464f89e91c458f164b83a454c6df67a2953873e8a785a4720a2253d843f88e5"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-rondpoint/Cargo.toml b/third_party/rust/uniffi-example-rondpoint/Cargo.toml index 64232cf2d2..44d9628df4 100644 --- a/third_party/rust/uniffi-example-rondpoint/Cargo.toml +++ b/third_party/rust/uniffi-example-rondpoint/Cargo.toml @@ -25,15 +25,15 @@ crate-type = [ ] [dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" [dev-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["bindgen-tests"] [build-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["build"] diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py index ecfcc1e527..0b47c0fa5a 100644 --- a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py +++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py @@ -2,7 +2,7 @@ import sys import ctypes from rondpoint import * -dico = Dictionnaire(Enumeration.DEUX, True, 0, 123456789) +dico = Dictionnaire(un=Enumeration.DEUX, deux=True, petit_nombre=0, gros_nombre=123456789) copyDico = copie_dictionnaire(dico) assert dico == copyDico diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb index 0121f6e0f9..faa4062019 100644 --- a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb +++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb @@ -6,7 +6,12 @@ require 'rondpoint' include Test::Unit::Assertions include Rondpoint -dico = Dictionnaire.new Enumeration::DEUX, true, 0, 123_456_789 +dico = Dictionnaire.new( + un: Enumeration::DEUX, + deux: true, + petit_nombre: 0, + gros_nombre: 123_456_789 +) assert_equal dico, Rondpoint.copie_dictionnaire(dico) diff --git a/third_party/rust/uniffi-example-sprites/.cargo-checksum.json b/third_party/rust/uniffi-example-sprites/.cargo-checksum.json index 3542d7cfd7..8a890fb65c 100644 --- a/third_party/rust/uniffi-example-sprites/.cargo-checksum.json +++ b/third_party/rust/uniffi-example-sprites/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"e2d06b68b0865f390496bffce512d19d5fc473b64ff23c705daabb4169b8301c","build.rs":"7e9d92d7c8fc17b359a29117b137ffc4d32f6c10b450d03e30a396339d8c9099","src/lib.rs":"d7984c0c10011b1bd939bce71dae7437ebb9090583b5d1b1cc133c2e5f60ab66","src/sprites.udl":"bfd35f04ba0549301189dfb8fc45b0f39bad00956c324f33be0e845fb7ff78aa","tests/bindings/test_sprites.kts":"06ed115325f37ce59ed6f33e2d651cd2aa352fddcc644580f62a6da6ca075844","tests/bindings/test_sprites.py":"2e6ce838cfb387586257703c3500062438e840dd7ae57d185cdc244dc0745b8f","tests/bindings/test_sprites.rb":"6289a1833c7c8f4583ee4f0488d680de2ee46cfb203095a9b66d7234e2f07d53","tests/bindings/test_sprites.swift":"b2c0a6f4d5edfd7de7c2ba77b838865ffda153a6f364f273456175192d3e6e00","tests/test_generated_bindings.rs":"9a22d693c97fc6d90031cc60f61ece1d9279165ad6a92c9fe937448e126e8de6"},"package":null}
\ No newline at end of file +{"files":{"Cargo.toml":"94385b28fdc7e33b7019fada486124c027d29b3d2b11b57e8d488658386f7974","build.rs":"7e9d92d7c8fc17b359a29117b137ffc4d32f6c10b450d03e30a396339d8c9099","src/lib.rs":"d7984c0c10011b1bd939bce71dae7437ebb9090583b5d1b1cc133c2e5f60ab66","src/sprites.udl":"bfd35f04ba0549301189dfb8fc45b0f39bad00956c324f33be0e845fb7ff78aa","tests/bindings/test_sprites.kts":"06ed115325f37ce59ed6f33e2d651cd2aa352fddcc644580f62a6da6ca075844","tests/bindings/test_sprites.py":"f976f2be7ab8b88e8b84def760a849c0d98f4c7b481f054f92b566325d78d52d","tests/bindings/test_sprites.rb":"009d545bb7167b7218211430cfaeeb143cc30617eedcf3e51baafe752ad43241","tests/bindings/test_sprites.swift":"b2c0a6f4d5edfd7de7c2ba77b838865ffda153a6f364f273456175192d3e6e00","tests/test_generated_bindings.rs":"9a22d693c97fc6d90031cc60f61ece1d9279165ad6a92c9fe937448e126e8de6"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-sprites/Cargo.toml b/third_party/rust/uniffi-example-sprites/Cargo.toml index 6caed17ab1..c6dc9d1f4b 100644 --- a/third_party/rust/uniffi-example-sprites/Cargo.toml +++ b/third_party/rust/uniffi-example-sprites/Cargo.toml @@ -25,15 +25,15 @@ crate-type = [ ] [dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" [dev-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["bindgen-tests"] [build-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["build"] diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py index 5142c2fc42..d04742e076 100644 --- a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py +++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py @@ -1,17 +1,17 @@ from sprites import * sempty = Sprite(None) -assert sempty.get_position() == Point(0, 0) +assert sempty.get_position() == Point(x=0, y=0) -s = Sprite(Point(0, 1)) -assert s.get_position() == Point(0, 1) +s = Sprite(Point(x=0, y=1)) +assert s.get_position() == Point(x=0, y=1) -s.move_to(Point(1, 2)) -assert s.get_position() == Point(1, 2) +s.move_to(Point(x=1, y=2)) +assert s.get_position() == Point(x=1, y=2) -s.move_by(Vector(-4, 2)) -assert s.get_position() == Point(-3, 4) +s.move_by(Vector(dx=-4, dy=2)) +assert s.get_position() == Point(x=-3, y=4) -srel = Sprite.new_relative_to(Point(0, 1), Vector(1, 1.5)) -assert srel.get_position() == Point(1, 2.5) +srel = Sprite.new_relative_to(Point(x=0, y=1), Vector(dx=1, dy=1.5)) +assert srel.get_position() == Point(x=1, y=2.5) diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb index 9d79b57026..fa73043979 100644 --- a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb +++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb @@ -7,16 +7,16 @@ include Test::Unit::Assertions include Sprites sempty = Sprite.new(nil) -assert_equal sempty.get_position, Point.new(0, 0) +assert_equal sempty.get_position, Point.new(x: 0, y: 0) -s = Sprite.new(Point.new(0, 1)) -assert_equal s.get_position, Point.new(0, 1) +s = Sprite.new(Point.new(x: 0, y: 1)) +assert_equal s.get_position, Point.new(x: 0, y: 1) -s.move_to(Point.new(1, 2)) -assert_equal s.get_position, Point.new(1, 2) +s.move_to(Point.new(x: 1, y: 2)) +assert_equal s.get_position, Point.new(x: 1, y: 2) -s.move_by(Vector.new(-4, 2)) -assert_equal s.get_position, Point.new(-3, 4) +s.move_by(Vector.new(dx: -4, dy: 2)) +assert_equal s.get_position, Point.new(x: -3, y: 4) -srel = Sprite.new_relative_to(Point.new(0, 1), Vector.new(1, 1.5)) -assert_equal srel.get_position, Point.new(1, 2.5) +srel = Sprite.new_relative_to(Point.new(x: 0, y: 1), Vector.new(dx: 1, dy: 1.5)) +assert_equal srel.get_position, Point.new(x: 1, y: 2.5) diff --git a/third_party/rust/uniffi-example-todolist/.cargo-checksum.json b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json index a0bf1d826c..6b3a53fc5e 100644 --- a/third_party/rust/uniffi-example-todolist/.cargo-checksum.json +++ b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"cb308f7bd5299ed45a3bd516390e21e28d0b02a1913e25917e30e9b8ebe084e3","build.rs":"709d073560a66876d2b975f5145a2dc36e1e3420d79328c8b62666526cae5b2d","src/lib.rs":"ccf6851beb2a3e481541dd8a3c3e3d2fbd513a410eef820221942098212a8184","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"f7430af9347df0daa954d38bc2203ce400affbb9a53fced4bb67a6796afa0664","tests/bindings/test_todolist.rb":"6524b5271a9cc0e2d78ca9f86ccb6973889926688a0843b4505a4f62d48f6dcb","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"46ef1fbedaac0c4867812ef2632a641eab36ab0ee12f5757567dd037aeddcbd3"},"package":null}
\ No newline at end of file +{"files":{"Cargo.toml":"9b182bf2d363240fe1f7b41c73da4bde96d87562812885e4f3175cb6b92ea56b","build.rs":"709d073560a66876d2b975f5145a2dc36e1e3420d79328c8b62666526cae5b2d","src/lib.rs":"ccf6851beb2a3e481541dd8a3c3e3d2fbd513a410eef820221942098212a8184","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"8ea1377fe336adaf4cade0fe073ae5825c3b38c63e46b7f4e92b87c0fe57c036","tests/bindings/test_todolist.rb":"e8840eb81477048718a56c5fa02c0fb2e28cf0a598a20c9393ba2262d6ffb89b","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"46ef1fbedaac0c4867812ef2632a641eab36ab0ee12f5757567dd037aeddcbd3"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/uniffi-example-todolist/Cargo.toml b/third_party/rust/uniffi-example-todolist/Cargo.toml index 9421e726a0..cf4de491cd 100644 --- a/third_party/rust/uniffi-example-todolist/Cargo.toml +++ b/third_party/rust/uniffi-example-todolist/Cargo.toml @@ -29,15 +29,15 @@ once_cell = "1.12" thiserror = "1.0" [dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" [dev-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["bindgen-tests"] [build-dependencies.uniffi] -version = "0.25" +version = "0.27" path = "../../uniffi" features = ["build"] diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py index 017e999fb2..7b676c83de 100644 --- a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py +++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py @@ -2,7 +2,7 @@ from todolist import * todo = TodoList() -entry = TodoEntry("Write bindings for strings in records") +entry = TodoEntry(text="Write bindings for strings in records") todo.add_item("Write python bindings") @@ -20,7 +20,7 @@ assert(todo.get_last_entry().text == "Write bindings for strings in records") todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") assert(todo.get_last() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") -entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") +entry2 = TodoEntry(text="Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") todo.add_entry(entry2) assert(todo.get_last_entry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb index d9e04f92e7..fc1a823f52 100644 --- a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb +++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb @@ -7,7 +7,7 @@ include Test::Unit::Assertions include Todolist todo = TodoList.new -entry = TodoEntry.new 'Write bindings for strings in records' +entry = TodoEntry.new(text: 'Write bindings for strings in records') todo.add_item('Write ruby bindings') @@ -25,7 +25,7 @@ assert_equal todo.get_last_entry.text, 'Write bindings for strings in records' todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣") assert_equal todo.get_last, "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣" -entry2 = TodoEntry.new("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") +entry2 = TodoEntry.new(text: "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣") todo.add_entry(entry2) assert_equal todo.get_last_entry.text, "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣" @@ -44,4 +44,4 @@ todo.add_item "Test liveness after being demoted from default" assert todo.get_last == "Test liveness after being demoted from default" todo2.add_item "Test shared state through local vs default reference" -assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference"
\ No newline at end of file +assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference" diff --git a/third_party/rust/uniffi/.cargo-checksum.json b/third_party/rust/uniffi/.cargo-checksum.json index 74d539b505..98c4aca54e 100644 --- a/third_party/rust/uniffi/.cargo-checksum.json +++ b/third_party/rust/uniffi/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"5a5cf41b9eb4aac8e312fc9584c0d47585a5a20b13bc7cfb14c9b8813ea596e6","release.toml":"1aa1b131d4cc93b5eba8758a4401c70bc0d7fe5861e2ec147e9259fe7c0da472","src/cli.rs":"0b4791c263d6cf54e4e63dff9a8ead59838d5e7b45fbf5b7f77ab16f602bdb0d","src/lib.rs":"6bc2c11f466fbcd128827a57b5f93a77f716262200f4e5ad2ed8dd75845320fc","tests/ui/proc_macro_arc.rs":"fedc429603753e8ef953642a7295323ccb3f76fd3ae1ab181ad90c5eb88212bb","tests/ui/proc_macro_arc.stderr":"a24af227b907328c9cac6317ec9f43dbc45d7f7c77c603e5d72db7fa050e8b01","tests/ui/version_mismatch.rs":"16ea359e5853517ee0d0704c015ae8c825533109fbefd715130d0f4a51f15898","tests/ui/version_mismatch.stderr":"21dcb836253312ba8e3a0502cce6ff279818aaaadcea9628a41b196e0c8c94b6"},"package":"21345172d31092fd48c47fd56c53d4ae9e41c4b1f559fb8c38c1ab1685fd919f"}
\ No newline at end of file +{"files":{"Cargo.toml":"eb974d356d4da93a076434ff428c448f70e036a724bd9a0a7eae6b9ddff2346e","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","release.toml":"1aa1b131d4cc93b5eba8758a4401c70bc0d7fe5861e2ec147e9259fe7c0da472","src/cli.rs":"5c0b9bb93665f2f49f7e90335e65206887e26e96f2a533eb1203be27c9380c84","src/lib.rs":"422503d7cbac1360852287b1810c99663669625b9abf080a5fec22058bb73d8c","tests/ui/proc_macro_arc.rs":"fedc429603753e8ef953642a7295323ccb3f76fd3ae1ab181ad90c5eb88212bb","tests/ui/proc_macro_arc.stderr":"a24af227b907328c9cac6317ec9f43dbc45d7f7c77c603e5d72db7fa050e8b01","tests/ui/version_mismatch.rs":"16ea359e5853517ee0d0704c015ae8c825533109fbefd715130d0f4a51f15898","tests/ui/version_mismatch.stderr":"21dcb836253312ba8e3a0502cce6ff279818aaaadcea9628a41b196e0c8c94b6"},"package":"a5566fae48a5cb017005bf9cd622af5236b2a203a13fb548afde3506d3c68277"}
\ No newline at end of file diff --git a/third_party/rust/uniffi/Cargo.toml b/third_party/rust/uniffi/Cargo.toml index 475c8ab9be..374e365502 100644 --- a/third_party/rust/uniffi/Cargo.toml +++ b/third_party/rust/uniffi/Cargo.toml @@ -12,11 +12,12 @@ [package] edition = "2021" name = "uniffi" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -41,18 +42,18 @@ features = [ optional = true [dependencies.uniffi_bindgen] -version = "=0.25.3" +version = "=0.27.1" optional = true [dependencies.uniffi_build] -version = "=0.25.3" +version = "=0.27.1" optional = true [dependencies.uniffi_core] -version = "=0.25.3" +version = "=0.27.1" [dependencies.uniffi_macros] -version = "=0.25.3" +version = "=0.27.1" [dev-dependencies.trybuild] version = "1" diff --git a/third_party/rust/uniffi/README.md b/third_party/rust/uniffi/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi/src/cli.rs b/third_party/rust/uniffi/src/cli.rs index b2d3adc2ae..77d7f219a9 100644 --- a/third_party/rust/uniffi/src/cli.rs +++ b/third_party/rust/uniffi/src/cli.rs @@ -5,6 +5,7 @@ use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; use uniffi_bindgen::bindings::TargetLanguage; +use uniffi_bindgen::BindingGeneratorDefault; // Structs to help our cmdline parsing. Note that docstrings below form part // of the "help" output. @@ -35,7 +36,7 @@ enum Commands { #[clap(long, short)] no_format: bool, - /// Path to the optional uniffi config file. If not provided, uniffi-bindgen will try to guess it from the UDL's file location. + /// Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence. #[clap(long, short)] config: Option<Utf8PathBuf>, @@ -95,21 +96,29 @@ pub fn run_main() -> anyhow::Result<()> { if lib_file.is_some() { panic!("--lib-file is not compatible with --library.") } - if config.is_some() { - panic!("--config is not compatible with --library. The config file(s) will be found automatically.") - } let out_dir = out_dir.expect("--out-dir is required when using --library"); if language.is_empty() { panic!("please specify at least one language with --language") } uniffi_bindgen::library_mode::generate_bindings( - &source, crate_name, &language, &out_dir, !no_format, + &source, + crate_name, + &BindingGeneratorDefault { + target_languages: language, + try_format_code: !no_format, + }, + config.as_deref(), + &out_dir, + !no_format, )?; } else { uniffi_bindgen::generate_bindings( &source, config.as_deref(), - language, + BindingGeneratorDefault { + target_languages: language, + try_format_code: !no_format, + }, out_dir.as_deref(), lib_file.as_deref(), crate_name.as_deref(), diff --git a/third_party/rust/uniffi/src/lib.rs b/third_party/rust/uniffi/src/lib.rs index 0625bd9c66..319b3c7836 100644 --- a/third_party/rust/uniffi/src/lib.rs +++ b/third_party/rust/uniffi/src/lib.rs @@ -17,8 +17,11 @@ pub use uniffi_bindgen::bindings::ruby::run_test as ruby_run_test; pub use uniffi_bindgen::bindings::swift::run_test as swift_run_test; #[cfg(feature = "bindgen")] pub use uniffi_bindgen::{ - bindings::TargetLanguage, generate_bindings, generate_component_scaffolding, - generate_component_scaffolding_for_crate, print_repr, + bindings::kotlin::gen_kotlin::KotlinBindingGenerator, + bindings::python::gen_python::PythonBindingGenerator, + bindings::ruby::gen_ruby::RubyBindingGenerator, + bindings::swift::gen_swift::SwiftBindingGenerator, bindings::TargetLanguage, generate_bindings, + generate_component_scaffolding, generate_component_scaffolding_for_crate, print_repr, }; #[cfg(feature = "build")] pub use uniffi_build::{generate_scaffolding, generate_scaffolding_for_crate}; diff --git a/third_party/rust/uniffi_bindgen/.cargo-checksum.json b/third_party/rust/uniffi_bindgen/.cargo-checksum.json index 880d79a96f..40b0b6e3cc 100644 --- a/third_party/rust/uniffi_bindgen/.cargo-checksum.json +++ b/third_party/rust/uniffi_bindgen/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"5285a403f48ecf7a073cbe1839c7afbd0e14998e8315b1ff8ecefb9e7171fc4e","askama.toml":"1a245b7803adca782837e125c49100147d2de0d5a1c949ff95e91af1701f6058","src/backend/config.rs":"4861dbf251dbb10beb1ed7e3eea7d79499a0de1cd9ce9ee8381a0e729c097dea","src/backend/filters.rs":"2da4eaa9af92e449f2fa20d06fc2ab2f758a9a10d3ad6cb8a94975184d40d2ff","src/backend/mod.rs":"2ee9d974cd259f7fb156028b4f4f7601691e94fb5692a6daf0d362df3ecf79a8","src/backend/types.rs":"598df3a861f5d53b2c710848943f6049dd43cb4f37aa81f2c08fd36fc5b2f5d5","src/bindings/kotlin/gen_kotlin/callback_interface.rs":"a6c7796ca66cbaabeef401b939d3c707bba17a77581da36a3a0b46f87630440e","src/bindings/kotlin/gen_kotlin/compounds.rs":"ebd2111a74032b336e0768facfb756a9422da2f9b413ee929b24c1c4315e6a06","src/bindings/kotlin/gen_kotlin/custom.rs":"7e619f7320796ecd8c4ced82904b4bd3c6a0043b55d5829274ab3040cdf9cd7f","src/bindings/kotlin/gen_kotlin/enum_.rs":"6559bb00d8e359126b016e549263c0c9bc1dfc5654ed662c0c2912b47931b1e4","src/bindings/kotlin/gen_kotlin/executor.rs":"58a192123fd2dd4b625f29d95ae6bf5161c2fef7bf50aa8790c3ae0e7a9430d9","src/bindings/kotlin/gen_kotlin/external.rs":"bcd2a44f2559a124aa287944ab59296239033372c6b4a7a3b625b1d41c441de2","src/bindings/kotlin/gen_kotlin/miscellany.rs":"6541987e627c4ff27a17ebe919b2b5cd97cb66ff41187ed636396b4e35ea2414","src/bindings/kotlin/gen_kotlin/mod.rs":"ccae80314058df0b4988d0965ab62b0dc872e8676592e7b55037cd54011e6854","src/bindings/kotlin/gen_kotlin/object.rs":"539ec05386c1e844bef09d4de8374760daa5ba99b009615c04be9c3927feb4c9","src/bindings/kotlin/gen_kotlin/primitives.rs":"2c3020416384a67855ca5086e485c4db6d7dcc3ce51343217b4a914b318ae350","src/bindings/kotlin/gen_kotlin/record.rs":"96fd1a180095a062b4a9b71d4f603b232f0133f46355a3e427c4064701d900f2","src/bindings/kotlin/gen_kotlin/variant.rs":"d111d6888745195fc2c24bdddc57359e771616102a8d182c5c8ad268b0a13460","src/bindings/kotlin/mod.rs":"ef88eb9b5b7d6f920c62a525ea4d4bf2a3b1a9154afaa012cdb2feea597fbf23","src/bindings/kotlin/templates/Async.kt":"064ce385ac0e68719de625e0172908257714b62da296ff14b2c0153b9966212a","src/bindings/kotlin/templates/BooleanHelper.kt":"28e8a5088c8d58c9bfdbc575af8d8725060521fdd7d092684a8044b24ae567c7","src/bindings/kotlin/templates/ByteArrayHelper.kt":"6fbb424556f631beb7f28c4168c568ad840445496a29d5c8f40a9a591b1661b1","src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt":"29fab10a8f6b699471e793e8d53f5bed74803a8c433ff80ccef5f56cf742c54a","src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt":"3b063ea03959c95327b6082c7edc0db0df83ee8b9e8d643b92ca45cf4fda458a","src/bindings/kotlin/templates/CustomTypeTemplate.kt":"be9bdc716731f3935a4d48728e33bfeb4acd514f3719ddbb273adcd6fb4ab31f","src/bindings/kotlin/templates/DurationHelper.kt":"414a98161538a26f3a9b357353270c1f245ad6ceed99496aca7162cf473a92fd","src/bindings/kotlin/templates/EnumTemplate.kt":"865fb1badd1a128390903ab8d9f42f9208c6db0eac5e53b88207282176cfd67f","src/bindings/kotlin/templates/ErrorTemplate.kt":"394c0093c0c86a0f2a14cd5fa60a70ba9970c65448867b6aca86fc25cfe08a4c","src/bindings/kotlin/templates/ExternalTypeTemplate.kt":"40df49116f9ea227c9a64a4f45bb7c2e99275c62e93f75290808e2c930911fba","src/bindings/kotlin/templates/FfiConverterTemplate.kt":"aa22962aaa9f641d48ccf44cb56d9f8a7736cbfaa01e1a1656662cfe5dd5c1d7","src/bindings/kotlin/templates/Float32Helper.kt":"662d95af3b629d143fb4d47cb7e9aa26ed28a5f3846de0341e28b0f9fb08bc25","src/bindings/kotlin/templates/Float64Helper.kt":"a77d099fa7d91e8702c1700e7949ffb6aaba9c6e8041ff48bab34b8e1fc9a0aa","src/bindings/kotlin/templates/ForeignExecutorTemplate.kt":"09c63a67adb8c6cb807108f02d7695d3425401ea0cc51b582cfd469a322fcce0","src/bindings/kotlin/templates/Helpers.kt":"90a11ec576e82265ba0f95fc330053779aada5976477f0d4a6b38619da1282cf","src/bindings/kotlin/templates/Int16Helper.kt":"7f83c4a48e1f3b2a59a3ca6a2662be8bc9baf3a5a748b31223cb3f51721ef249","src/bindings/kotlin/templates/Int32Helper.kt":"e02e4702175554b09fd2dd6ac3089dcd2c395f08ec60e762159566a9c9889450","src/bindings/kotlin/templates/Int64Helper.kt":"7a6fd6ca486852c89399c699935a9dfa1c32b9356d9a965cfde532581f05d9fa","src/bindings/kotlin/templates/Int8Helper.kt":"0554545494b6b9a76ce94f9c1723f8cf4230a13076feb75d620b1c9ca1ac4668","src/bindings/kotlin/templates/MapTemplate.kt":"07d20d8cf58a4bca950ac22dbec5e3471ac6c18c3cca562e45628de827b03ccf","src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt":"22226bb8dde52f12c77b40bbb8f7ceb7dcef313950b8d42b2f5fe44777158bfc","src/bindings/kotlin/templates/ObjectRuntime.kt":"7f38f54a0c889d7534d23afdace6b87b6ced5c024a36b5450078a06e071caad8","src/bindings/kotlin/templates/ObjectTemplate.kt":"3f3baea52b6923446827ea1ee3f5160edfe81e00c11f61ea1f72dbc6b796a6d8","src/bindings/kotlin/templates/OptionalTemplate.kt":"c916c4545d37087ee01a6b6aef966928691d26be539c388fce608e9e3ff4b0e7","src/bindings/kotlin/templates/RecordTemplate.kt":"8d573856de75b55b985594ac4e4a6f08da931dce6b52420654b5bb080eec414f","src/bindings/kotlin/templates/RustBufferTemplate.kt":"002878ce9ce9924231e55853b86768fc3dec2caef6a28098f01c3edd739ca076","src/bindings/kotlin/templates/SequenceTemplate.kt":"477a0f6714af151ca58cfc7c4f2cf0e878d391dd9db4efe964f19d5ad4f544f0","src/bindings/kotlin/templates/StringHelper.kt":"ec0441da90a394616d0ba3492eca50602161fe42062bc4f60e9a23191e71e009","src/bindings/kotlin/templates/TimestampHelper.kt":"353c2890f06ad6dda238e9aebb4bdff7bb838e17e46abf351ed3ff1fbc4e6580","src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt":"888ef82e2637ab104f0821803666c77212b5d5940414f71e899f8f8968ffe572","src/bindings/kotlin/templates/Types.kt":"4d87ef529f666db532fb5355a339fd50be3edd6225a703564455b81ef4d4a16d","src/bindings/kotlin/templates/UInt16Helper.kt":"e84a1f30a5a899ba2c5db614d3f3c74f25bccf6dd99bf68b8830829332d051e9","src/bindings/kotlin/templates/UInt32Helper.kt":"7cdf08cc580046935f27ba07b53685968608a102e0a6be305111037c63d7ddf8","src/bindings/kotlin/templates/UInt64Helper.kt":"fd7baacbf3ab6202ff83edcc66e5f7beb11a10053ba66d0b49547616cc7cbe1f","src/bindings/kotlin/templates/UInt8Helper.kt":"bbf5a6d66c995aea9fe2fa9840c6bfa78b03520a09469b984f0e1d43191e453a","src/bindings/kotlin/templates/macros.kt":"0f64366a9d7523b3d20d7e9d8b04eb064568772dec529a12f878acf5d2246a41","src/bindings/kotlin/templates/wrapper.kt":"d515ca22d12f13b1b99c5daa411ea35d9a288076e4b7208eaf88170e5da7477c","src/bindings/kotlin/test.rs":"0f752ab0afde20194afca07af94da9d1422300032696d5f845cd864fc63c5d51","src/bindings/mod.rs":"949f323d6eb5c018497103dbb9dcffb8f395eb5960694b551a24b4887e853afb","src/bindings/python/gen_python/callback_interface.rs":"5df3e091d3c88ef7645e570f693942161a9b9c6307419c15a2534fbc5da974af","src/bindings/python/gen_python/compounds.rs":"9b7187d35826e1b12dbc2b16a13aec783a51f0952e3e2d24adaefbd0ac005016","src/bindings/python/gen_python/custom.rs":"81501641648eb638f5a338c01a71db0d0e96601c3dda83acdb2d49072b387d42","src/bindings/python/gen_python/enum_.rs":"7c3f8f6a97c1491175c8b93b8f9ab13748e2f8084bb717836b6935d024805439","src/bindings/python/gen_python/error.rs":"161bd2e041e3a63a91899de173eec8450cc10e1e9552d064969aa72a02fdfd5e","src/bindings/python/gen_python/executor.rs":"dbbf2292c79f73dacb317a8645185e42f328a017951662975c0488399c562058","src/bindings/python/gen_python/external.rs":"0325e9a39645eb5454d716d4db76a4a31083ddfafa8e9ca063257292372a0637","src/bindings/python/gen_python/miscellany.rs":"d6f6305dd0af85b7ba87b70cbe6ecba00c83d5082c5bdcaf25962fff853973ea","src/bindings/python/gen_python/mod.rs":"4ac0dc0fd9aaabf2e1f9245d13e0090ee0e9c1235ef203bec9314cac5974e9f0","src/bindings/python/gen_python/object.rs":"a4d4c20a0a52687feff2b9a547a13aa9bda09b3af9ec26508646658a88eec8b3","src/bindings/python/gen_python/primitives.rs":"b830c68e20d8539b8ac5566f1ca0dd262c1b14712a747f79e70004cd8f409ba1","src/bindings/python/gen_python/record.rs":"f8e12ce43d7e0f37f05420a849e7867b7251f9790933609a4cb99050fd063089","src/bindings/python/mod.rs":"eac32ce383460d58d3ccf1d406173465fc8a1db8a24408df67620b7d14dcd0cd","src/bindings/python/templates/Async.py":"4a35a878883a548f3bbed929a9ec74c133e2e9cf08375989503e73ddc2f9c648","src/bindings/python/templates/BooleanHelper.py":"c19e38ae3daa29a831f2394a0a2e74c924711e55ddc85db8ac9b5b8b6da9cd92","src/bindings/python/templates/BytesHelper.py":"e8fb9919acc784fb056bba4ab8d5c04ca7b2275211f8397ef2a391833e3d5e8f","src/bindings/python/templates/CallbackInterfaceRuntime.py":"795d8826d5d2b397a91c531c6b1b76d9425728efdcb90514170c8c4f35053e40","src/bindings/python/templates/CallbackInterfaceTemplate.py":"3f38e7b290fce198d188a63dcfa74486810153816283e683d6e04896c2dbea9a","src/bindings/python/templates/CustomType.py":"12064dde5e1baf4d78e541837c414cdc7ee9e827a284c54a98ac92dfaf3478e8","src/bindings/python/templates/DurationHelper.py":"271c301bc480cd48d5df2aec15789dd360f4d3098a9f360d7f8f33fa0a7fcd0a","src/bindings/python/templates/EnumTemplate.py":"1cbc2206f045c3050be1912df581a5393d6f0a4a79c96d8b49661696d25830e0","src/bindings/python/templates/ErrorTemplate.py":"f9ab6c910024e88ff92a7575d5d00cdafe448b2e85d07a9edea57ae7b6dc5864","src/bindings/python/templates/ExternalTemplate.py":"ed4d65caf2de3fd2c2a3fd2658eb44cf91cd2f0878c017be63afa4394bf56be3","src/bindings/python/templates/Float32Helper.py":"80c0a0619d2c58c100ea8db37125878c8e8cf56c42f77195aa9b4b6b6d5716c8","src/bindings/python/templates/Float64Helper.py":"62c3ed0d646d3383c890d1f8fd7cb8639433971b9ba9261ac43c1391472eb141","src/bindings/python/templates/ForeignExecutorTemplate.py":"6a7903acc65b9dac17524767d94b142d38d25d5f5bb27133ee9fd7ed8fb5f5a9","src/bindings/python/templates/Helpers.py":"3265eeb5917e0090a7dbd50fdbd12e7d1b1832a58b347cd003ecf8a433c9ebcd","src/bindings/python/templates/Int16Helper.py":"ef7fd0035a80aef556bdbfbcf074751d4e25661f4e07f9bf41f48601d171e5aa","src/bindings/python/templates/Int32Helper.py":"af7e0176ed41260089426498946e47565a7d57e98dffaae4562dfe541c3019d1","src/bindings/python/templates/Int64Helper.py":"171908319be9edcfe3b178d1d74f0173df2aae6a4a92895a2079fa476caed7df","src/bindings/python/templates/Int8Helper.py":"d02a4a5452ec1096b1b1953e4d661d699f9f8f0ce5086a6f3577a0119f479666","src/bindings/python/templates/MapTemplate.py":"fd0bd7e396a6288a16ecb3dae087ab725be556789887a4dc0c00ab97d815f3fb","src/bindings/python/templates/NamespaceLibraryTemplate.py":"b78b7161d43a95f5a0b5d7da6cae0d8bf47017c6c77ad210e15be9531413baa9","src/bindings/python/templates/ObjectTemplate.py":"3af0c737d1b482169d62dee1b6c49f1a18ea3f485e9fc323b070f35cf00c242c","src/bindings/python/templates/OptionalTemplate.py":"59df962441fb1c50cb99be014c87ecf69450c1a3b60fa6763bd40e9e948124b1","src/bindings/python/templates/PointerManager.py":"22faf6a2801cf756f3b09415b597f0cd403a3872ac99a7e44e3b7b6217606cd7","src/bindings/python/templates/RecordTemplate.py":"04fcbd662bc9817597366046e09321d2516eb8240e796dd9b6f971c347475429","src/bindings/python/templates/RustBufferHelper.py":"8a8c20d195534e465a173dc778ae98957c50e209ec824af2d2b143f5ba6061f5","src/bindings/python/templates/RustBufferTemplate.py":"f5e247b0f8988f29ce94ab50b5fe7749d0423cda1d37b6145cc8a6c5a9818449","src/bindings/python/templates/SequenceTemplate.py":"047f19074fe08982b59001da2ba7318b331ce431d73503e111330579a3ba065f","src/bindings/python/templates/StringHelper.py":"a3f874ea9330413e854c2ebbeff5507e32a166de6967c5cea63ede1f021e267a","src/bindings/python/templates/TimestampHelper.py":"de099ce51ceaa86519c28bb38e21933ead36ff341f4907695029212bcdfed3ac","src/bindings/python/templates/TopLevelFunctionTemplate.py":"93b6101fae2cafdf1a9325bed07019609ac35bacef2dc31ba4be5c256d827473","src/bindings/python/templates/Types.py":"feb69d895e9279e52479146e1dfe2fec48c547378ef2cc0fb988f6acaf6bcc63","src/bindings/python/templates/UInt16Helper.py":"5fbb30ece1f9a2680b60baf680ec4e2936d64de2ff107018e751ef1c041443ef","src/bindings/python/templates/UInt32Helper.py":"84207c380e38a38bb919d58769384a0f4fa175ebbd04ac451b37ccfe01ff68a1","src/bindings/python/templates/UInt64Helper.py":"4606f381834740319a9f604a418ba149917a6dbd43d3d3d8da50c655893e2c8e","src/bindings/python/templates/UInt8Helper.py":"aecf7cf08b7dc75fe81e8dcac78dfce166b132d5c20b4301d845c479ab9f49ba","src/bindings/python/templates/macros.py":"7d0d08f418edf65ff365bf1fb37e3132aebb720dfecdcef3e3ce50529fe0eb6b","src/bindings/python/templates/wrapper.py":"91e8cbf18e5b7d0b2be31c0e09b230318d19406c316b453dab973341eb2c6add","src/bindings/python/test.rs":"48e93959ce3e34ff0191126416301b170239d3e2665711da786e0b8b7a90a2ab","src/bindings/ruby/gen_ruby/mod.rs":"7f3a94537c331a941e6e010e35563247f11f1fedaf971cb8538e17797cb17efa","src/bindings/ruby/gen_ruby/tests.rs":"7dcb86b08e643c43503f4cac6396833497f6988b004321c0067700ee29ffbf32","src/bindings/ruby/mod.rs":"0fdfab5306dc5c05fbcbfb273340d96ad70c5caf5074834ad6851af1a9a56734","src/bindings/ruby/templates/EnumTemplate.rb":"5480edb347f5829e478d19474691babd72f37616ed846d519b5a61cb1d6cf047","src/bindings/ruby/templates/ErrorTemplate.rb":"301c177e639f0a27f19d4935c5317e672aadecbee2f9bfa778df982320f5148d","src/bindings/ruby/templates/Helpers.rb":"ce7ed4be97dad507b991c69c28dc7bb6427e5e79a4b2fba9dad9dccabc3e090c","src/bindings/ruby/templates/NamespaceLibraryTemplate.rb":"9a24c427b9eba99d9e13181a5559a385b5d1d16beae2b72a402f2645b22a9048","src/bindings/ruby/templates/ObjectTemplate.rb":"0cfd9438e4821cf2164b23d748b3227a8cffbe2fab5b7eb70832228ccb628ee0","src/bindings/ruby/templates/RecordTemplate.rb":"4aeff886928ca972e5dc9b799581b30c66a6f6dce446af3285dd3ed6b422dea9","src/bindings/ruby/templates/RustBufferBuilder.rb":"8da4e425b36dde4f171b238cbe57e02fb55e91a45a82134c1dccc0fc360733c0","src/bindings/ruby/templates/RustBufferStream.rb":"43ad2defc772fd24b68df0736533c26597ba007e89b6a5ba0d31fbe356648151","src/bindings/ruby/templates/RustBufferTemplate.rb":"405a32592cab145175b64e21398f83e6e0f16354552c0395479270e732910e08","src/bindings/ruby/templates/TopLevelFunctionTemplate.rb":"88213e7e25bef664da939c04dd5621f438af735ffcb4d2d0c24a529538630069","src/bindings/ruby/templates/macros.rb":"79d7d0e9af749dadbf242f37c0f86af7c616ea5318da127747def40f6cdb20d1","src/bindings/ruby/templates/wrapper.rb":"f82b41543546f8e5804cd0e1785f4735d9dd95383566d0e5ba1cd4d9e8c0578d","src/bindings/ruby/test.rs":"d19837119725233bd9971ca2dfc3256156071c64e6dfaf07ad2307432c055bbb","src/bindings/swift/gen_swift/callback_interface.rs":"6b51276350f506f96fefd0ae8cb3afdcd514e8a529d9e982afc68cbf68d74578","src/bindings/swift/gen_swift/compounds.rs":"1aba37cf2f438423a4ce476eea6a36f71f1d5daddbb77c88556bc3abde287ca7","src/bindings/swift/gen_swift/custom.rs":"bddb601b4ea8810ecbad01271d5ec0b3958999b09bc9382c83637dfd43451734","src/bindings/swift/gen_swift/enum_.rs":"87be67ec3394616368d9ef8e99b7f234c053b3bee9a7f9e6f2dff37f147c8837","src/bindings/swift/gen_swift/executor.rs":"ab672e2d05acbc2c4a839af22034aa557d5e69f1d9c913158310ea1e93851557","src/bindings/swift/gen_swift/external.rs":"321974136d58e649e60b2a3f70a369dce2d49f474f79579f8e0d66eb63d2d634","src/bindings/swift/gen_swift/miscellany.rs":"7fc2444596d76545ad82ee6c4bed64a29dd4a0438d50bfaafe511f41f6a0e409","src/bindings/swift/gen_swift/mod.rs":"41e4bf2fbe622d0dba85363455e287e00dc48e4043910cef35c30ce170acf52b","src/bindings/swift/gen_swift/object.rs":"2269f65a6b58a24bd08fedb133a38b37663bcf11d0586c50a67028022706a156","src/bindings/swift/gen_swift/primitives.rs":"c8346601008ac6a6d07f08ec7395182c45a4d86c163dc1d6d9c326c49f2acda1","src/bindings/swift/gen_swift/record.rs":"5ad98ab04a5d8178daf0956db819c87d26aae7bf968184e88d512e34c02feb90","src/bindings/swift/mod.rs":"26ba270cb7913661f3cee703038d1ea4a70bff64c3b31351d6bc77e67cdee20d","src/bindings/swift/templates/Async.swift":"84b9be2b5eca2dcfad7eee0cb8d34fec613a4bfdc8a7170b8d11575e457f567b","src/bindings/swift/templates/BooleanHelper.swift":"f02e391bed44ca0e03c66c4e6a1545caaae490fc72c4cf5935e66220082a8c30","src/bindings/swift/templates/BridgingHeaderTemplate.h":"3f468869e77b9293836822b8f2ac348716ab5d487f7b8fef1a87ec30ddafa8d5","src/bindings/swift/templates/CallbackInterfaceRuntime.swift":"2c71ac715ad0bca6f73559748453ba37ca242c90de38f76876989be05d21c49b","src/bindings/swift/templates/CallbackInterfaceTemplate.swift":"790f50b49d5b07dd44e8b215ed1fff02991c1f65aba9a9a9925275a544348813","src/bindings/swift/templates/CustomType.swift":"71520eb38a4be9035dca9e3e0402f386e7eaa79b28568bbc2f20d3fd53b4544d","src/bindings/swift/templates/DataHelper.swift":"df11547a2df57dcca0ff9cddc691bb5fa07d5ffd3d328d1c3b4443078008b111","src/bindings/swift/templates/DurationHelper.swift":"cbc41aaa58bda6c2313ede36a9f656a01a28f9c72aa1624e0e1c1da7b841ffb6","src/bindings/swift/templates/EnumTemplate.swift":"fe205dd28defea8ed6126a45b2a95240a920dfebda8927134a50c3b6d0d7e9d7","src/bindings/swift/templates/ErrorTemplate.swift":"3dddb278763b75b38294c1165522fa91078a951ce05c91fbdfde43b5a097f34f","src/bindings/swift/templates/Float32Helper.swift":"ea32538058c4b3c72b1cd2530ac00d0349fadab5e1bc617d33aae4c87692fc98","src/bindings/swift/templates/Float64Helper.swift":"e27e9424dc6e97b8cacc6ca4c796dd2d16dcfcb877e2f19c45eca03381a41e78","src/bindings/swift/templates/ForeignExecutorTemplate.swift":"205933825e691fec525286d263ea34d592cc462257764ee76325bf98cb3cd240","src/bindings/swift/templates/Helpers.swift":"a88fd909787b855998671e551cdb3284109e2fd2b2e7492b1c93c82aad0e9d35","src/bindings/swift/templates/Int16Helper.swift":"204906911813a3931436057c23032f4c4e39e023df90d641d6c6086aefe2f820","src/bindings/swift/templates/Int32Helper.swift":"0997f059c9c4edd3c41aee0bbad4aa2bda6d791a0d623ad8014d5aa6bdae718d","src/bindings/swift/templates/Int64Helper.swift":"bcf8c2deebb3ee9bce87735adc4bd100981989943b69f6a7fb499a9aec4c25d9","src/bindings/swift/templates/Int8Helper.swift":"ad1ec0fa213724933fa4dc4e2e304e13ac4722b774bfffac44793986b997dd33","src/bindings/swift/templates/MapTemplate.swift":"53971ec388417b02519f8deb8d66361ab4693eae77d116f6051cbea4738054ec","src/bindings/swift/templates/ModuleMapTemplate.modulemap":"99ad1e9bf550a21497296f9248ecd4385dd6d0b5892951d24cf990cdbf3eec2c","src/bindings/swift/templates/ObjectTemplate.swift":"4633572ac6d27a0f82f3b125c2136ad4fa126391c0b64b5db1bde4b04a77f807","src/bindings/swift/templates/OptionalTemplate.swift":"2376487ceadca3975f0e82ddf6ce61af8bbbf5b0592fa9cd977460f148d8c99d","src/bindings/swift/templates/RecordTemplate.swift":"16e0b98354b624a8922d7d384a005fa660a6a388d70381872ebbcd0de9fb78a4","src/bindings/swift/templates/RustBufferTemplate.swift":"f4422fdf0cb5b4db267d461f063dedc319ea1a5a13bae1b82c3f108ba8c658bb","src/bindings/swift/templates/SequenceTemplate.swift":"8425b279197582a94b4cf363ab0463907a68a624e24045720ef7df2bcacf3383","src/bindings/swift/templates/StringHelper.swift":"968b9b9b7fbe06a2ac2143016edaff3552e201652accb8d613b03645f0d24a90","src/bindings/swift/templates/TimestampHelper.swift":"82eece13aa186c8e3745c8ad2f1290639ca9689573018a2bdc5c75afbae58c26","src/bindings/swift/templates/TopLevelFunctionTemplate.swift":"ffe0287861e67dcad2be77f30c00c0a326ab59ffbd37409de3bafc969d49df26","src/bindings/swift/templates/Types.swift":"98c654bfc5d2d4ece965cfe15b00e7151b815e26bfb55abf17e799efffccc2c0","src/bindings/swift/templates/UInt16Helper.swift":"d6fba577324fc0e9e50329c968df99341de418011be126bd29702f8a94d87c02","src/bindings/swift/templates/UInt32Helper.swift":"5e0cf151a0c40098b3d96815ba3de23e15fe52f3c517577e1b0c7e7a4c12428f","src/bindings/swift/templates/UInt64Helper.swift":"17237b38d09ced8d2a8ff1ad9ca86873a19e417280e0e60f33d7063397ea4b7b","src/bindings/swift/templates/UInt8Helper.swift":"c4cb2ee4a78b54ca8d0013383c6b43e9ecd42776e3dc7d6e40086325d41714e5","src/bindings/swift/templates/macros.swift":"438091831b355b0ba6726dab7c17c0687ca58854c7799e946a8012e95a42f4ba","src/bindings/swift/templates/wrapper.swift":"d83a1b8ac3ffc761d4e560adae57d5ad275e0fd1bf3fdc35f3b3b3699317c6e6","src/bindings/swift/test.rs":"c15d19e7f324613e2dbd7995dffba875ed430919a0882f05e8e1f6cc8aea613c","src/interface/callbacks.rs":"34384f1a4e89cd30e2c35beab2bbd8cc0a9e3dd21ed0b390859610fbe209b16a","src/interface/enum_.rs":"3a4f9e77d80128444a8a226ef1e5ad7cd339f8d815aa7012554cf94c2e4edd98","src/interface/ffi.rs":"fa23a2e6fcd89d956523bd0aa5c946138ac629752f0be9085e09f78aade75fac","src/interface/function.rs":"835ee542cb19c67fb95502a790771e54deb712272baed8bdc3f6bfabd9c69d6f","src/interface/mod.rs":"5bd3b5a2dd91382089323a21d4d0419dd5621799add4786a4710df0afebcca69","src/interface/object.rs":"2364c4e7f887d6e4256d065f9ad049ffad19f92fe1c1d76789c33a22376bdd7f","src/interface/record.rs":"931dbaa8cb440debfcf14d51be3e6c517ba2c28655033ad2486e384a9bea25c4","src/interface/universe.rs":"66deaa33394e401e3005670731a0685068302fc01640655335a7cbe9fb4cf48e","src/lib.rs":"64a6239dbaa8196089600e7b0c577b43eefc4a9935893292298bf400fd77a03d","src/library_mode.rs":"89c9fc47db09ccb779fd2842d3c7e07b7863164bb5585eeaf959490ca5555438","src/macro_metadata/ci.rs":"07482b87bf5912277d114b24ad3e560cac94ca0087bd6ee9bee2c328d992bb18","src/macro_metadata/extract.rs":"a106a5b6ad8e5deba474646162b83ec4065796c58c60fc13dfbf1a72ed713833","src/macro_metadata/mod.rs":"bcb5e9a015510e9d74c288da928a4bfb8d80926a8ff85227c0eae8cdb2605519","src/scaffolding/mod.rs":"65749e72181c63edae55d49493aa49a1efece93c0a27deda230f5de4b9ba7d60","src/scaffolding/templates/CallbackInterfaceTemplate.rs":"0581f257c0eb7f0be922ef3ebf7892aafad15e9dc7865e53fc9396375f3188fb","src/scaffolding/templates/Checksums.rs":"ee926e840875c2e48e1d0cc5185c11f7a1ed3bde5264b07540812cb13c1d7481","src/scaffolding/templates/EnumTemplate.rs":"350cdcb4f23be6e6e2b442e0d4ea65549bb35a407bef1ac745a37a2e2b527fae","src/scaffolding/templates/ErrorTemplate.rs":"b93e8bc08d818fbf8976b766635ae192042b78c85f8086d4ee41ae03ab7f3b6f","src/scaffolding/templates/ExternalTypesTemplate.rs":"6cf8a89d9e6f1b6f5af4bc86e49454323fa4981846ec76d6661fcff41c348a8e","src/scaffolding/templates/ObjectTemplate.rs":"df614156bee529fd28905a6ad2270685e51736eade6af856edba5de6e9484141","src/scaffolding/templates/RecordTemplate.rs":"5b0f351739d5770f874fb7da56700c557df1d73ac219f3b18c4212d26fc422e0","src/scaffolding/templates/ReexportUniFFIScaffolding.rs":"aa8a1ffa98b6033707d965f90b5709474ed6bc79486fb47dacae8417fc056cf8","src/scaffolding/templates/TopLevelFunctionTemplate.rs":"96b99b38d074492673797737ddac0683c803a3908e03cc9b999d16f7a76ed178","src/scaffolding/templates/UdlMetadata.rs":"d7c50af1de92ef85630b385a910c7b29875502d622eb90da5541a7012b93d9e2","src/scaffolding/templates/macros.rs":"ea6bacd8dd9116ad739bdafe893d70407050f35e4a7ac8dd2c78b8ef34263e8e","src/scaffolding/templates/scaffolding_template.rs":"c8e18306a73ec5b764f665660fc5c91d498b63b6c3f489e524b2bae50f81f231"},"package":"fd992f2929a053829d5875af1eff2ee3d7a7001cb3b9a46cc7895f2caede6940"}
\ No newline at end of file +{"files":{"Cargo.toml":"037dacd80bb367cfc530c5ca19fbfac091f385cf88ad5bd33c2009fde6d06e3e","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","askama.toml":"1a245b7803adca782837e125c49100147d2de0d5a1c949ff95e91af1701f6058","src/backend/config.rs":"4861dbf251dbb10beb1ed7e3eea7d79499a0de1cd9ce9ee8381a0e729c097dea","src/backend/filters.rs":"8a818952896f9c5f438d6705bb28f56e77fbfce6d9757a2d74e1b3a925ed36e1","src/backend/mod.rs":"2ee9d974cd259f7fb156028b4f4f7601691e94fb5692a6daf0d362df3ecf79a8","src/backend/types.rs":"598df3a861f5d53b2c710848943f6049dd43cb4f37aa81f2c08fd36fc5b2f5d5","src/bindings/kotlin/gen_kotlin/callback_interface.rs":"741100c2b4b484583d34408b276394078a24918c47101fbaa6233df9c4da32f2","src/bindings/kotlin/gen_kotlin/compounds.rs":"b40d1ab8c70d7da458ff45d2ce58efb6cc3b24bf560c093cbec7d0854d461dc4","src/bindings/kotlin/gen_kotlin/custom.rs":"7e619f7320796ecd8c4ced82904b4bd3c6a0043b55d5829274ab3040cdf9cd7f","src/bindings/kotlin/gen_kotlin/enum_.rs":"6559bb00d8e359126b016e549263c0c9bc1dfc5654ed662c0c2912b47931b1e4","src/bindings/kotlin/gen_kotlin/external.rs":"38f42be67105b9a2ca5ecefec959e6659af728bedb9d107a51c595fe6ff5d332","src/bindings/kotlin/gen_kotlin/miscellany.rs":"6541987e627c4ff27a17ebe919b2b5cd97cb66ff41187ed636396b4e35ea2414","src/bindings/kotlin/gen_kotlin/mod.rs":"34328d11c59a67159620a21bc660fae149bbf452a817d6c9d3f7657cc5b79134","src/bindings/kotlin/gen_kotlin/object.rs":"1cb8d1f5eaf06ceaadb6d2cedca482fdd1502c24500ba270d8fcac48ef2f1231","src/bindings/kotlin/gen_kotlin/primitives.rs":"249896ec7d18f0f8d1d5dc8dc66ea6f3d0cc7b13344ab6892fb985f339d99b9f","src/bindings/kotlin/gen_kotlin/record.rs":"96fd1a180095a062b4a9b71d4f603b232f0133f46355a3e427c4064701d900f2","src/bindings/kotlin/gen_kotlin/variant.rs":"d111d6888745195fc2c24bdddc57359e771616102a8d182c5c8ad268b0a13460","src/bindings/kotlin/mod.rs":"ef88eb9b5b7d6f920c62a525ea4d4bf2a3b1a9154afaa012cdb2feea597fbf23","src/bindings/kotlin/templates/Async.kt":"2130ef176ffabe85cc737059203f2bda38df1f22f2398aa338c9d3099e6d46e2","src/bindings/kotlin/templates/BooleanHelper.kt":"50d8a5109e2d2676f25a02772079efbaac61776a76e3e84eebd1fb13294842de","src/bindings/kotlin/templates/ByteArrayHelper.kt":"dc4aafffacb1fa8f3b4e15f714c13b8d715eec178c63bdba6260baf612dd80d8","src/bindings/kotlin/templates/CallbackInterfaceImpl.kt":"be4d5a5d3ed4f7b1d4c9822905c5732bdb8593c3dbf8d4aabab62291d7ccec99","src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt":"76689c1bfa8aa7dc6e2c9e77c42212b9f317763fb35cd7704ca470675dd2648d","src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt":"b497250899bfd0c79bd01d77f23454b12b108fa269055d6f3699be74fb93d015","src/bindings/kotlin/templates/CustomTypeTemplate.kt":"d42eba4334c39749037d14ef9e2219a2e515479c18905df3f49534424317c848","src/bindings/kotlin/templates/DurationHelper.kt":"dfb45fe1b47bc04dd8c70cb98531c40606eca554791132ee6bed2846f8ee099c","src/bindings/kotlin/templates/EnumTemplate.kt":"7cefbb1e29d4e89420f6a95275bbab891984a56978cf4852a1e52bcc82afd9e8","src/bindings/kotlin/templates/ErrorTemplate.kt":"8f41de90753a42cfe33ba837997baa2954208b987e70cec13ecb4124faf25aa6","src/bindings/kotlin/templates/ExternalTypeTemplate.kt":"b1df8566d000431bfc3820a2e455426e810cba6d8683e77ab78ab0bb7d003720","src/bindings/kotlin/templates/FfiConverterTemplate.kt":"bc0bdbc99ee2459f50c84abf6c1bb236a6179eaf519f1c5f5b3f72d8184dc662","src/bindings/kotlin/templates/Float32Helper.kt":"789246343d34594fc39072c1a5393b848cecadb353659fc6e9080fc7e760fc21","src/bindings/kotlin/templates/Float64Helper.kt":"b87eac72da313b1d559b1738bba1c771f43bb7566fdbb3a34546dd56beeb5832","src/bindings/kotlin/templates/HandleMap.kt":"feb456ea4dfb2ad07331d49d606faf396737817c6f6712a2d9a9d843daebdc1e","src/bindings/kotlin/templates/Helpers.kt":"e7657732f44e8092d492ef291e3ce11aa803531d82be99645b8513c00dca99ac","src/bindings/kotlin/templates/Int16Helper.kt":"54bb1aefbcae1c3c10e0cdc6a9d45e070e3ca57c9ffa53b2d65e5c59808c9743","src/bindings/kotlin/templates/Int32Helper.kt":"49b3e5274d5c7853d227ec986d0a0c71621a448391b2c9aaf4351b86f310dadf","src/bindings/kotlin/templates/Int64Helper.kt":"264fd99a4109f0b2c40b806fc1a0181f27331346a02d94e398f1e34110e3414b","src/bindings/kotlin/templates/Int8Helper.kt":"a50c315d8474a212914f10f43ca7e75061bb73067e0de686b182891c6c7f1bc3","src/bindings/kotlin/templates/Interface.kt":"c66912069c3f61848bbee3d1886abbfa74895f759853f6b4a3c62cef5976766c","src/bindings/kotlin/templates/MapTemplate.kt":"f7e0360d3be74e543573bd56925bf25c6c22e6203aecc1cf519464704eeeb0ee","src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt":"db4c30cfcb709c5413892cf3cf69391ba36c6160543274e8d1f2bedf9001d058","src/bindings/kotlin/templates/ObjectCleanerHelper.kt":"9ebcfcb3fe7788e93cf8cba30fd7470b363719e9ed25cdde43c95aebe1b90c2a","src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt":"049e1e32a23b7923393e3dcabce49532737d44e9dbb331f62984ada67bde3125","src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt":"b6287f72afdb0ab9af5e56136c28e6a4f5e18a50305bce8923ee061b9406cfc3","src/bindings/kotlin/templates/ObjectTemplate.kt":"6a776feb36b0379c43e0013a26ba85cdef385aa1e59b4c2efa7a794140aa99bb","src/bindings/kotlin/templates/OptionalTemplate.kt":"918f2029e60710f4b048a77830b12b388c917af1a488c9f05f38323c58ee0f9e","src/bindings/kotlin/templates/README.md":"83587ff54a31fa47d2c0849cb5db52d6f079551e1cfb73c76c6dd02a7b164ad9","src/bindings/kotlin/templates/RecordTemplate.kt":"677bb63ae4fae9117e9c77928370a8911ab959c6b884c6af2ba4efc686c52721","src/bindings/kotlin/templates/RustBufferTemplate.kt":"b3b78b2c41cbfff6262d758f9ebe064e76d20841ec4db7705142449f7ffc75a9","src/bindings/kotlin/templates/SequenceTemplate.kt":"c1aa28ca87528c97c62656f850205023c2eb8d218264ef7b1e70207ab4f1b9b6","src/bindings/kotlin/templates/StringHelper.kt":"4e942e36af05dc823d5f28ab336c55ff86bf0f95bbb748399bcaa8ad291c7032","src/bindings/kotlin/templates/TimestampHelper.kt":"70137e78de18796996889e8d648f1e90830aa652a93a0b4639ff9f7ccb967a25","src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt":"68c714cc8c7fa244166c5902a59c90317cdfc402193624cade405e3454f9bf67","src/bindings/kotlin/templates/Types.kt":"c725f7e57eda5b2d52c3c92b24f93d9321591e72c89d16973163b3b8d713b85b","src/bindings/kotlin/templates/UInt16Helper.kt":"ee96270f426933cfcd914894d4c7895544f7e3d4a7c24be78afc2896b46cbcbe","src/bindings/kotlin/templates/UInt32Helper.kt":"b2d7543098277e7b92502a0a6693dc25dd42e360f776b19987a48dd7fc6db7fd","src/bindings/kotlin/templates/UInt64Helper.kt":"fc855eb78a4b50d76fc53509dae8218c48a221db5bf73cf5368d755fb9aae478","src/bindings/kotlin/templates/UInt8Helper.kt":"af22d9e6f99fe9d8d7d5175cb03f7a9f62628c9dd939dbbfb5a4085359e52e0e","src/bindings/kotlin/templates/macros.kt":"0a221962503f6977b129eb3c1e3772e3e9d51cbab6d813c55b0387c24d784184","src/bindings/kotlin/templates/wrapper.kt":"a02028a86c620679602f26714c7feb4a306867cda1cba8240ca6e83d99cebd91","src/bindings/kotlin/test.rs":"28bf88a9e9aa9510adbe78005a2027a62818433f49426172046dc83a3ad41911","src/bindings/mod.rs":"949f323d6eb5c018497103dbb9dcffb8f395eb5960694b551a24b4887e853afb","src/bindings/python/gen_python/callback_interface.rs":"5df3e091d3c88ef7645e570f693942161a9b9c6307419c15a2534fbc5da974af","src/bindings/python/gen_python/compounds.rs":"4a83b02e11ae969ab360ba61df44d91dc790f372b5960b350b0b019a57d19de4","src/bindings/python/gen_python/custom.rs":"81501641648eb638f5a338c01a71db0d0e96601c3dda83acdb2d49072b387d42","src/bindings/python/gen_python/enum_.rs":"7c3f8f6a97c1491175c8b93b8f9ab13748e2f8084bb717836b6935d024805439","src/bindings/python/gen_python/error.rs":"161bd2e041e3a63a91899de173eec8450cc10e1e9552d064969aa72a02fdfd5e","src/bindings/python/gen_python/external.rs":"d7101124c22dd7837e227a7f1b683c57b92229a2cd5b25b06740f2fe3d76bed5","src/bindings/python/gen_python/miscellany.rs":"d6f6305dd0af85b7ba87b70cbe6ecba00c83d5082c5bdcaf25962fff853973ea","src/bindings/python/gen_python/mod.rs":"b8aac9a146551cd660f1cd310f8ef02e3bd4a11540a087551bbb7c7706b99e16","src/bindings/python/gen_python/object.rs":"a4d4c20a0a52687feff2b9a547a13aa9bda09b3af9ec26508646658a88eec8b3","src/bindings/python/gen_python/primitives.rs":"b830c68e20d8539b8ac5566f1ca0dd262c1b14712a747f79e70004cd8f409ba1","src/bindings/python/gen_python/record.rs":"f8e12ce43d7e0f37f05420a849e7867b7251f9790933609a4cb99050fd063089","src/bindings/python/mod.rs":"eac32ce383460d58d3ccf1d406173465fc8a1db8a24408df67620b7d14dcd0cd","src/bindings/python/templates/Async.py":"f1cf32d8e28b5e2fcbad6ccd00d03fd49f4b54eac47adcfe23cbf786d523eee7","src/bindings/python/templates/BooleanHelper.py":"cf7bcd414197258b0cfa54c6ad2aeb81a1a6a4a45af5b6aaf6f8e484bc5af59d","src/bindings/python/templates/BytesHelper.py":"8c39cf1760678316cf2b3903632f2bacae4f8aaa961b37eeb03e06e9f07241d1","src/bindings/python/templates/CallbackInterfaceImpl.py":"7dbb049ffebc3565ffb4605d53843c69f782f3e86472e060e3194be4986d328b","src/bindings/python/templates/CallbackInterfaceRuntime.py":"54dbda8a6ffe284ef2045da290a69d37974fed672eb57309c9fc7ac665969397","src/bindings/python/templates/CallbackInterfaceTemplate.py":"ef235bd7927592eb19a2db422352a435b7466595ef31e4822a16c3caa24cdda6","src/bindings/python/templates/CustomType.py":"4647a60dbe63ead2b23d07cf3a3a4a190a219d81357532364fd4afdf990d6e1b","src/bindings/python/templates/DurationHelper.py":"eb9278b546f79b71525ae61a5b30bfe4a1260fd2268c87c600d157bf9b0e2a44","src/bindings/python/templates/EnumTemplate.py":"49903d969b8b160d8f1a0747c803d5f54a6f000a6781493eacad1f6ca7811d7a","src/bindings/python/templates/ErrorTemplate.py":"d7af297596e5ef894e3fdaeb92bd6446843c987f8283c77973bd10fce537c9c1","src/bindings/python/templates/ExternalTemplate.py":"0cd36fc89f0a587dadfe0cb89c4d45a641822ba07cb9410299bdcd73ad3edb79","src/bindings/python/templates/Float32Helper.py":"4aa522163f121fcb84d2f024774d8dd9321c31f09b9a95da3a3131b6d2756971","src/bindings/python/templates/Float64Helper.py":"e7fa247fd9c3907b818f0d1ba28c2cee897e75fdd07fdacad1b8a2b5c26ba418","src/bindings/python/templates/HandleMap.py":"9dbfdcb4ddde5927fd9b9fb26b5194bc16b1d2280c2259895fd0ea443af4afd6","src/bindings/python/templates/Helpers.py":"09ddd46d6fcc6ee7e9b1c123b0830426c967f94e22ab18b3ee248b873f7d5ebc","src/bindings/python/templates/Int16Helper.py":"613345b35e63e7284caf97de9630747ec9cdadc8dd3f8451d2e878cb762958f5","src/bindings/python/templates/Int32Helper.py":"758b093b66dc0a8d3f0b13b9388d21f47de31b5e948689041c4d43ef98cf2c4f","src/bindings/python/templates/Int64Helper.py":"c7e76441ee14e78e856f8819f73243bc04b33ec16083ae7390e0ec27141855f2","src/bindings/python/templates/Int8Helper.py":"d963a76b218a32ea2b3bb26f265dbbc47e859b7d1bc939b43fd9b93c51a62292","src/bindings/python/templates/MapTemplate.py":"9dc81ebced353d0137ef6fe3187e170e3e72d32a3b5520dbbcc1f95354ebf62d","src/bindings/python/templates/NamespaceLibraryTemplate.py":"e480a80a27ed5e54a3ff9c72d3d6ab13343764da6c413d813c4bd72429139193","src/bindings/python/templates/ObjectTemplate.py":"976aa726baf36b53d1c319b262c34b8b2de2e414cb8d3c645ed3bf006833b9e8","src/bindings/python/templates/OptionalTemplate.py":"2629f3b46ff394df620bbff1699935e6844d9aa017e74ac43c0b38acd05f8d42","src/bindings/python/templates/Protocol.py":"8446fe51d7c9d16d7086694cee8016c6f571dc5c930fd18848fadcf109aa0566","src/bindings/python/templates/RecordTemplate.py":"c99d10cc061af339349bb0c7e8b67223fdcd9064362badc137a2ad0df17c57c0","src/bindings/python/templates/RustBufferHelper.py":"a48e5ed1dcde19993ae50bec9b881afa3bc6dd5f7d8257fd60214f2100224929","src/bindings/python/templates/RustBufferTemplate.py":"017f31fd5075306f5c8c2bd0e3a21ea965c694c0daf2523187ab076fc786e9ca","src/bindings/python/templates/SequenceTemplate.py":"1b262e5f546a1923de6968e0233cc621a5fae16062e9e6ac874c9b62d8f145df","src/bindings/python/templates/StringHelper.py":"b303b7fcbbc0981a28c6a7d0cc5bd90f8e9c8b8d572792e217a324b2bdb95dbd","src/bindings/python/templates/TimestampHelper.py":"b3da14de54822f44ada4459355c842550b944b3cd2a85a4eac0f59e82d646877","src/bindings/python/templates/TopLevelFunctionTemplate.py":"1d9da1b6ca2175b30f3277a46a1749590490e82bee6b990ff35efb04e5f102ef","src/bindings/python/templates/Types.py":"3653e2cf773493c6ddfd13ef298b0c7cb33fabc1dba495fca64b9287aae03042","src/bindings/python/templates/UInt16Helper.py":"8ffe4b69a5d4a2b3c5677ff1d8954efc67ab67713ffe297380e930e0379d493d","src/bindings/python/templates/UInt32Helper.py":"83f9603aceae05f2134c7183313ab0a1a8f64cabd8070ae19557494fe41dd6d2","src/bindings/python/templates/UInt64Helper.py":"97269025377a256e821e57991b07e17af05f4d1c4228e01fe5f243d784cb509d","src/bindings/python/templates/UInt8Helper.py":"4896723ed0ab8f5aef4a58d599e0a0dbd63d373f5740821c21b4b429b6a7afda","src/bindings/python/templates/macros.py":"d766feb4dedd2d0e4cd2052da7a69c0b074b97f880b857ee457faa43975230ed","src/bindings/python/templates/wrapper.py":"ab05168e3d01d1a26e9589cd9855d7776c46c59d699f1402a29dfae6eb9ebfbc","src/bindings/python/test.rs":"69d3ee230820f38d743438c8212e1bfc4e92f948d9e73548a38c093e164b2759","src/bindings/ruby/gen_ruby/mod.rs":"861be105f9001d4ad8f7b8ac4a303a95459ec7de7a0c2fdac14a083c43d5a07c","src/bindings/ruby/gen_ruby/tests.rs":"7dcb86b08e643c43503f4cac6396833497f6988b004321c0067700ee29ffbf32","src/bindings/ruby/mod.rs":"0fdfab5306dc5c05fbcbfb273340d96ad70c5caf5074834ad6851af1a9a56734","src/bindings/ruby/templates/EnumTemplate.rb":"5480edb347f5829e478d19474691babd72f37616ed846d519b5a61cb1d6cf047","src/bindings/ruby/templates/ErrorTemplate.rb":"301c177e639f0a27f19d4935c5317e672aadecbee2f9bfa778df982320f5148d","src/bindings/ruby/templates/Helpers.rb":"ce7ed4be97dad507b991c69c28dc7bb6427e5e79a4b2fba9dad9dccabc3e090c","src/bindings/ruby/templates/NamespaceLibraryTemplate.rb":"9a24c427b9eba99d9e13181a5559a385b5d1d16beae2b72a402f2645b22a9048","src/bindings/ruby/templates/ObjectTemplate.rb":"a1c0cc38865195d61df3540284f4756f1b6406b205d74e3855e7089d763b2791","src/bindings/ruby/templates/RecordTemplate.rb":"343a4b159cf298045747fb48f17552e3bf2c9775fa5b4fa40b424976dc67e33a","src/bindings/ruby/templates/RustBufferBuilder.rb":"a36d9183f3e66cbbb1c3e584b78ab86e01bd6b89a4a5ef9614c5df24dc383acc","src/bindings/ruby/templates/RustBufferStream.rb":"ab4fc736906e320fca56dca280daf40138ba443d957c42fbf5cfbf1c6acf463a","src/bindings/ruby/templates/RustBufferTemplate.rb":"de577fbae811f72e260270656f2c12ad7a4d157c78f97898d0cd4e309d92ad6a","src/bindings/ruby/templates/TopLevelFunctionTemplate.rb":"26c9c2d53853792270795bd822e41968e995375478d246f808f9935af77a7d6a","src/bindings/ruby/templates/macros.rb":"dc60ed79844b04fe828a24aef3550a6b6c30f7c0b66f03608d7c56725287ceed","src/bindings/ruby/templates/wrapper.rb":"f82b41543546f8e5804cd0e1785f4735d9dd95383566d0e5ba1cd4d9e8c0578d","src/bindings/ruby/test.rs":"027d62085498b20977f025117e1fb7c30923a189961d679823f16ca62a575d0d","src/bindings/swift/gen_swift/callback_interface.rs":"1a2b56d16db841574be0762d66b57fbaef0519273d45c47ca687bf656546f201","src/bindings/swift/gen_swift/compounds.rs":"d62206bdab8a2a65b19342933efad54c171f0f8c217b82ee8b41617043662fe5","src/bindings/swift/gen_swift/custom.rs":"bddb601b4ea8810ecbad01271d5ec0b3958999b09bc9382c83637dfd43451734","src/bindings/swift/gen_swift/enum_.rs":"87be67ec3394616368d9ef8e99b7f234c053b3bee9a7f9e6f2dff37f147c8837","src/bindings/swift/gen_swift/external.rs":"a1d34b688679a74b0ddcfcb1147a7064b53883d9df9c0670f950078516492ee7","src/bindings/swift/gen_swift/miscellany.rs":"7fc2444596d76545ad82ee6c4bed64a29dd4a0438d50bfaafe511f41f6a0e409","src/bindings/swift/gen_swift/mod.rs":"e6c12506217d0a5479e946998a24ee984e4ea4c4f19334cbd014f53504300181","src/bindings/swift/gen_swift/object.rs":"45a6d6bb053f3ef397ab8c6feba8d0e126a8d14cd87597d25015f97c6ffc3417","src/bindings/swift/gen_swift/primitives.rs":"26a29ea764988d9e021bbac6505ef45e49ae42426522d6e3822e949b6f0b589c","src/bindings/swift/gen_swift/record.rs":"5ad98ab04a5d8178daf0956db819c87d26aae7bf968184e88d512e34c02feb90","src/bindings/swift/mod.rs":"26ba270cb7913661f3cee703038d1ea4a70bff64c3b31351d6bc77e67cdee20d","src/bindings/swift/templates/Async.swift":"1645ac8dbea8575dec05acf0aeb18e210f76231c36ea0178b183e02a3ff6e18f","src/bindings/swift/templates/BooleanHelper.swift":"f02e391bed44ca0e03c66c4e6a1545caaae490fc72c4cf5935e66220082a8c30","src/bindings/swift/templates/BridgingHeaderTemplate.h":"4e1e91859c4fc6f40db32648645f046fb7e71841f44ae84737ea85bdecff7fa3","src/bindings/swift/templates/CallbackInterfaceImpl.swift":"514a0932c445e4040460da2969e4f21595e17b9b960eb23c6d1526e47dd56c51","src/bindings/swift/templates/CallbackInterfaceRuntime.swift":"a5def6b3b41698a42e6ccf5c85d365fe0abc7eff629d9f49d9d396ee90aad3a0","src/bindings/swift/templates/CallbackInterfaceTemplate.swift":"4dcab3e590f897499782aef3c657b9b838b312d8b49a018bf0f1ebde15ada786","src/bindings/swift/templates/CustomType.swift":"71520eb38a4be9035dca9e3e0402f386e7eaa79b28568bbc2f20d3fd53b4544d","src/bindings/swift/templates/DataHelper.swift":"df11547a2df57dcca0ff9cddc691bb5fa07d5ffd3d328d1c3b4443078008b111","src/bindings/swift/templates/DurationHelper.swift":"cbc41aaa58bda6c2313ede36a9f656a01a28f9c72aa1624e0e1c1da7b841ffb6","src/bindings/swift/templates/EnumTemplate.swift":"4b980f8bfe65266d27d561e88c7d79d87f426b35b4b842ef80c5d56841e2f672","src/bindings/swift/templates/ErrorTemplate.swift":"1233d119320a44dbf6099681595dda9bf5dd2a1474af4380b704bff0563c38ef","src/bindings/swift/templates/Float32Helper.swift":"ea32538058c4b3c72b1cd2530ac00d0349fadab5e1bc617d33aae4c87692fc98","src/bindings/swift/templates/Float64Helper.swift":"e27e9424dc6e97b8cacc6ca4c796dd2d16dcfcb877e2f19c45eca03381a41e78","src/bindings/swift/templates/HandleMap.swift":"acd2b06d678e64a573f7b842c7d08b87140ddb5d7146c0bf3401d99999399ec2","src/bindings/swift/templates/Helpers.swift":"491553eb82cdc5c944451a541d4e4655537cccb961f220783459b57b2311ca84","src/bindings/swift/templates/Int16Helper.swift":"204906911813a3931436057c23032f4c4e39e023df90d641d6c6086aefe2f820","src/bindings/swift/templates/Int32Helper.swift":"0997f059c9c4edd3c41aee0bbad4aa2bda6d791a0d623ad8014d5aa6bdae718d","src/bindings/swift/templates/Int64Helper.swift":"bcf8c2deebb3ee9bce87735adc4bd100981989943b69f6a7fb499a9aec4c25d9","src/bindings/swift/templates/Int8Helper.swift":"ad1ec0fa213724933fa4dc4e2e304e13ac4722b774bfffac44793986b997dd33","src/bindings/swift/templates/MapTemplate.swift":"53971ec388417b02519f8deb8d66361ab4693eae77d116f6051cbea4738054ec","src/bindings/swift/templates/ModuleMapTemplate.modulemap":"99ad1e9bf550a21497296f9248ecd4385dd6d0b5892951d24cf990cdbf3eec2c","src/bindings/swift/templates/ObjectTemplate.swift":"37e57815e60900ae48b953fe01e01535d4ab8076f6160fc93c37dd08fdee47a4","src/bindings/swift/templates/OptionalTemplate.swift":"2376487ceadca3975f0e82ddf6ce61af8bbbf5b0592fa9cd977460f148d8c99d","src/bindings/swift/templates/Protocol.swift":"2614b1378cadf14e7617fedd7367c227ac2a774d528acd3a42e44fd0c4f58528","src/bindings/swift/templates/RecordTemplate.swift":"f9f576b72fda9d1e1db34d1765ec6ec8206103a297329720c1c9a1f58ad085b5","src/bindings/swift/templates/RustBufferTemplate.swift":"89ed33846c0cfb220e823a1002238b16f006f3170d8de0dbbf7775d4f8143c31","src/bindings/swift/templates/SequenceTemplate.swift":"8425b279197582a94b4cf363ab0463907a68a624e24045720ef7df2bcacf3383","src/bindings/swift/templates/StringHelper.swift":"968b9b9b7fbe06a2ac2143016edaff3552e201652accb8d613b03645f0d24a90","src/bindings/swift/templates/TimestampHelper.swift":"82eece13aa186c8e3745c8ad2f1290639ca9689573018a2bdc5c75afbae58c26","src/bindings/swift/templates/TopLevelFunctionTemplate.swift":"7aa473a5b12ad7623f61d6c31f6879f269f51d2c4134dd899ce24c7b31ef35f1","src/bindings/swift/templates/Types.swift":"15e255e35e267f2aca49ed5a4fe16ef79520f4261433fd30c5e6c7f637a4d3f6","src/bindings/swift/templates/UInt16Helper.swift":"d6fba577324fc0e9e50329c968df99341de418011be126bd29702f8a94d87c02","src/bindings/swift/templates/UInt32Helper.swift":"5e0cf151a0c40098b3d96815ba3de23e15fe52f3c517577e1b0c7e7a4c12428f","src/bindings/swift/templates/UInt64Helper.swift":"17237b38d09ced8d2a8ff1ad9ca86873a19e417280e0e60f33d7063397ea4b7b","src/bindings/swift/templates/UInt8Helper.swift":"c4cb2ee4a78b54ca8d0013383c6b43e9ecd42776e3dc7d6e40086325d41714e5","src/bindings/swift/templates/macros.swift":"b30ffd93fe2213e13c3b9910bf2404403b4b231d4cd32c81e0f76c3bb4d151b5","src/bindings/swift/templates/wrapper.swift":"e553af470320391d150e6489eac549064689a37e5db6947914ce5609d0128031","src/bindings/swift/test.rs":"f55ba6c05c250093b26ae91404fd9200951462c1cd99e6b2718f7fb4ebcb7fbb","src/interface/callbacks.rs":"4a019376ec8fbaec495a9e3a1d5cb079af65767b6d85bc9f508f92a1e7f5344f","src/interface/enum_.rs":"7baee60e02cc7f751d7a941e877c10a6afaffea626e79897a0e8b17702f13c15","src/interface/ffi.rs":"11b48aaf22fd9cd9eeded30afe950b26cc1c6d8ec6f9385c9e4cd3bdb2881f43","src/interface/function.rs":"be0f9f268e1947381fa235c5a0cf3c1965fd73121172d31f9c130acf539f2ac0","src/interface/mod.rs":"b97b11295b91691e7e6b7b023bba019729ad02f2204bba460c48acf62c5ee363","src/interface/object.rs":"d37d55edc62f52cf7fac4e3b8be1e46557dcbcfa8eb2e5998a91be2c6c062d92","src/interface/record.rs":"d8ddf873c35beaff45ab522bc4cb809c459a7937fd4061dae8c2db0db4c4edb4","src/interface/universe.rs":"76f368ff2b5326c517025a460405d343618bcc9fc9cfb28346313c8f7a335050","src/lib.rs":"2e3adccd5f0a3dacce6e533edfb5640ddee05e4f87ce8144cba859a14af219f6","src/library_mode.rs":"43ee55e4bb8d27dcec8a164961f22de941603d79d4e10c270ba9e7a751d92a1b","src/macro_metadata/ci.rs":"fa87ef42065c821aa89d3fb7938888c035ce6fc03bb3fce575a878c8e49012fb","src/macro_metadata/extract.rs":"7554c7b19b50d40e90bb503320311c2caa3b1e45e4376a9266ce32c0d48afffb","src/macro_metadata/mod.rs":"bcb5e9a015510e9d74c288da928a4bfb8d80926a8ff85227c0eae8cdb2605519","src/scaffolding/mod.rs":"66c3f2d9e81ded234fdd5b34cbb1da334cc271fa6ff3daa41b9acf9ac02f2194","src/scaffolding/templates/CallbackInterfaceTemplate.rs":"11acee064df46f7b5132401ae49c03c77f296bc04065085d6fd5c4ad6b628718","src/scaffolding/templates/Checksums.rs":"ee926e840875c2e48e1d0cc5185c11f7a1ed3bde5264b07540812cb13c1d7481","src/scaffolding/templates/EnumTemplate.rs":"305b8f0e6ec38300f0ae576a1bf1c576d0088d0df8d0b45818ad25f0216a7ac0","src/scaffolding/templates/ErrorTemplate.rs":"e6ec4e1d4c594d9f14a8dfe0a24103a66c0cc91d2129f0e1644775740f85bea1","src/scaffolding/templates/ExternalTypesTemplate.rs":"4c45cefca1774de3f3b650ce3b9a1b1b8fc10c62e0e48e54ac300748c32959f6","src/scaffolding/templates/ObjectTemplate.rs":"80689b74cbb426e6ce8bce77359b122d747b34b48d9a30aef44f93c9aa726fa7","src/scaffolding/templates/RecordTemplate.rs":"644177d86b52bf39c277b4e60a66f594b3fb0454f6b62837f9041297135c09c9","src/scaffolding/templates/ReexportUniFFIScaffolding.rs":"aa8a1ffa98b6033707d965f90b5709474ed6bc79486fb47dacae8417fc056cf8","src/scaffolding/templates/TopLevelFunctionTemplate.rs":"c11a688cafc2e21c3be105533b34c1f73eab55202936f7c8a97191d7e27f26e0","src/scaffolding/templates/UdlMetadata.rs":"d7c50af1de92ef85630b385a910c7b29875502d622eb90da5541a7012b93d9e2","src/scaffolding/templates/macros.rs":"ea6bacd8dd9116ad739bdafe893d70407050f35e4a7ac8dd2c78b8ef34263e8e","src/scaffolding/templates/scaffolding_template.rs":"c8e18306a73ec5b764f665660fc5c91d498b63b6c3f489e524b2bae50f81f231"},"package":"4a77bb514bcd4bf27c9bd404d7c3f2a6a8131b957eba9c22cfeb7751c4278e09"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_bindgen/Cargo.toml b/third_party/rust/uniffi_bindgen/Cargo.toml index 9469a9cf25..f0c7af8665 100644 --- a/third_party/rust/uniffi_bindgen/Cargo.toml +++ b/third_party/rust/uniffi_bindgen/Cargo.toml @@ -12,11 +12,12 @@ [package] edition = "2021" name = "uniffi_bindgen" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust (codegen and cli tooling)" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -54,7 +55,7 @@ version = "2.7.0" version = "0.3" [dependencies.goblin] -version = "0.6" +version = "0.8" [dependencies.heck] version = "0.4" @@ -67,15 +68,19 @@ version = "1.0" [dependencies.serde] version = "1" +features = ["derive"] + +[dependencies.textwrap] +version = "0.16" [dependencies.toml] version = "0.5" [dependencies.uniffi_meta] -version = "=0.25.3" +version = "=0.27.1" [dependencies.uniffi_testing] -version = "=0.25.3" +version = "=0.27.1" [dependencies.uniffi_udl] -version = "=0.25.3" +version = "=0.27.1" diff --git a/third_party/rust/uniffi_bindgen/README.md b/third_party/rust/uniffi_bindgen/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_bindgen/src/backend/filters.rs b/third_party/rust/uniffi_bindgen/src/backend/filters.rs index 0d2da8cab2..f4dde0e420 100644 --- a/third_party/rust/uniffi_bindgen/src/backend/filters.rs +++ b/third_party/rust/uniffi_bindgen/src/backend/filters.rs @@ -13,12 +13,12 @@ use std::fmt; // Need to define an error that implements std::error::Error, which neither String nor // anyhow::Error do. #[derive(Debug)] -struct UniFFIError { +pub struct UniFFIError { message: String, } impl UniFFIError { - fn new(message: String) -> Self { + pub fn new(message: String) -> Self { Self { message } } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs index e20020e87c..ae4bffc973 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs @@ -26,6 +26,6 @@ impl CodeType for CallbackInterfaceCodeType { } fn initialization_fn(&self) -> Option<String> { - Some(format!("{}.register", self.ffi_converter_name())) + Some(format!("uniffiCallbackInterface{}.register", self.id)) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs index 4329f32f4c..8d075bbedb 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs @@ -5,55 +5,81 @@ use super::{AsCodeType, CodeType}; use crate::backend::{Literal, Type}; use crate::ComponentInterface; -use paste::paste; -fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String { - match literal { - Literal::Null => "null".into(), - Literal::EmptySequence => "listOf()".into(), - Literal::EmptyMap => "mapOf()".into(), +#[derive(Debug)] +pub struct OptionalCodeType { + inner: Type, +} - // For optionals - _ => super::KotlinCodeOracle.find(inner).literal(literal, ci), +impl OptionalCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } + fn inner(&self) -> &Type { + &self.inner } } -macro_rules! impl_code_type_for_compound { - ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => { - paste! { - #[derive(Debug)] - pub struct $T { - inner: Type, - } - - impl $T { - pub fn new(inner: Type) -> Self { - Self { inner } - } - fn inner(&self) -> &Type { - &self.inner - } - } - - impl CodeType for $T { - fn type_label(&self, ci: &ComponentInterface) -> String { - format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci)) - } - - fn canonical_name(&self) -> String { - format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name()) - } - - fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { - render_literal(literal, self.inner(), ci) - } - } +impl CodeType for OptionalCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!( + "{}?", + super::KotlinCodeOracle.find(self.inner()).type_label(ci) + ) + } + + fn canonical_name(&self) -> String { + format!( + "Optional{}", + super::KotlinCodeOracle.find(self.inner()).canonical_name() + ) + } + + fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { + match literal { + Literal::None => "null".into(), + Literal::Some { inner } => super::KotlinCodeOracle.find(&self.inner).literal(inner, ci), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } - } +} -impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}"); -impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}"); +#[derive(Debug)] +pub struct SequenceCodeType { + inner: Type, +} + +impl SequenceCodeType { + pub fn new(inner: Type) -> Self { + Self { inner } + } + fn inner(&self) -> &Type { + &self.inner + } +} + +impl CodeType for SequenceCodeType { + fn type_label(&self, ci: &ComponentInterface) -> String { + format!( + "List<{}>", + super::KotlinCodeOracle.find(self.inner()).type_label(ci) + ) + } + + fn canonical_name(&self) -> String { + format!( + "Sequence{}", + super::KotlinCodeOracle.find(self.inner()).canonical_name() + ) + } + + fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String { + match literal { + Literal::EmptySequence => "listOf()".into(), + _ => panic!("Invalid literal for List type: {literal:?}"), + } + } +} #[derive(Debug)] pub struct MapCodeType { @@ -92,7 +118,10 @@ impl CodeType for MapCodeType { ) } - fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String { - render_literal(literal, &self.value, ci) + fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String { + match literal { + Literal::EmptyMap => "mapOf()".into(), + _ => panic!("Invalid literal for Map type: {literal:?}"), + } } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs deleted file mode 100644 index 154e12a381..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs +++ /dev/null @@ -1,24 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use super::CodeType; -use crate::ComponentInterface; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self, _ci: &ComponentInterface) -> String { - // Kotlin uses a CoroutineScope for ForeignExecutor - "CoroutineScope".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } - - fn initialization_fn(&self) -> Option<String> { - Some("FfiConverterForeignExecutor.register".into()) - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs index 3ecf09d47f..d55c78f760 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs @@ -17,8 +17,8 @@ impl ExternalCodeType { } impl CodeType for ExternalCodeType { - fn type_label(&self, _ci: &ComponentInterface) -> String { - self.name.clone() + fn type_label(&self, ci: &ComponentInterface) -> String { + super::KotlinCodeOracle.class_name(ci, &self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index 1ed0575a9a..c4fc8e0ed6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -7,20 +7,21 @@ use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use crate::backend::TemplateExpression; +use crate::bindings::kotlin; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; @@ -28,6 +29,28 @@ mod primitives; mod record; mod variant; +pub struct KotlinBindingGenerator; +impl BindingGenerator for KotlinBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + kotlin::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Kotlin requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} + trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in /// method signatures and property declarations. @@ -73,10 +96,21 @@ trait CodeType: Debug { pub struct Config { package_name: Option<String>, cdylib_name: Option<String>, + generate_immutable_records: Option<bool>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, #[serde(default)] external_packages: HashMap<String, String>, + #[serde(default)] + android: bool, + #[serde(default)] + android_cleaner: Option<bool>, +} + +impl Config { + pub(crate) fn android_cleaner(&self) -> bool { + self.android_cleaner.unwrap_or(self.android) + } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -103,6 +137,11 @@ impl Config { "uniffi".into() } } + + /// Whether to generate immutable records (`val` instead of `var`) + pub fn generate_immutable_records(&self) -> bool { + self.generate_immutable_records.unwrap_or(false) + } } impl BindingsConfig for Config { @@ -236,7 +275,6 @@ pub struct KotlinWrapper<'a> { ci: &'a ComponentInterface, type_helper_code: String, type_imports: BTreeSet<ImportRequirement>, - has_async_fns: bool, } impl<'a> KotlinWrapper<'a> { @@ -249,7 +287,6 @@ impl<'a> KotlinWrapper<'a> { ci, type_helper_code, type_imports, - has_async_fns: ci.has_async_fns(), } } @@ -258,10 +295,6 @@ impl<'a> KotlinWrapper<'a> { .iter_types() .map(|t| KotlinCodeOracle.find(t)) .filter_map(|ct| ct.initialization_fn()) - .chain( - self.has_async_fns - .then(|| "uniffiRustFutureContinuationCallback.register".into()), - ) .collect() } @@ -301,7 +334,12 @@ impl KotlinCodeOracle { /// Get the idiomatic Kotlin rendering of a variable name. fn var_name(&self, nm: &str) -> String { - format!("`{}`", nm.to_string().to_lower_camel_case()) + format!("`{}`", self.var_name_raw(nm)) + } + + /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`. + pub fn var_name_raw(&self, nm: &str) -> String { + nm.to_string().to_lower_camel_case() } /// Get the idiomatic Kotlin rendering of an individual enum variant. @@ -309,14 +347,78 @@ impl KotlinCodeOracle { nm.to_string().to_shouty_snake_case() } - fn ffi_type_label_by_value(ffi_type: &FfiType) -> String { + /// Get the idiomatic Kotlin rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + /// Get the idiomatic Kotlin rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)), + FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)), + _ => self.ffi_type_label(ffi_type), + } + } + + /// FFI type name to use inside structs + /// + /// The main requirement here is that all types must have default values or else the struct + /// won't work in some JNA contexts. + fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType) -> String { + match ffi_type { + // Make callbacks function pointers nullable. This matches the semantics of a C + // function pointer better and allows for `null` as a default value. + FfiType::Callback(name) => format!("{}?", self.ffi_callback_name(name)), + _ => self.ffi_type_label_by_value(ffi_type), + } + } + + /// Default values for FFI + /// + /// This is used to: + /// - Set a default return value for error results + /// - Set a default for structs, which JNA sometimes requires + fn ffi_default_value(&self, ffi_type: &FfiType) -> String { + match ffi_type { + FfiType::UInt8 | FfiType::Int8 => "0.toByte()".to_owned(), + FfiType::UInt16 | FfiType::Int16 => "0.toShort()".to_owned(), + FfiType::UInt32 | FfiType::Int32 => "0".to_owned(), + FfiType::UInt64 | FfiType::Int64 => "0.toLong()".to_owned(), + FfiType::Float32 => "0.0f".to_owned(), + FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "Pointer.NULL".to_owned(), + FfiType::RustBuffer(_) => "RustBuffer.ByValue()".to_owned(), + FfiType::Callback(_) => "null".to_owned(), + FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue()".to_owned(), + _ => unimplemented!("ffi_default_value: {ffi_type:?}"), + } + } + + fn ffi_type_label_by_reference(&self, ffi_type: &FfiType) -> String { match ffi_type { - FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)), - _ => Self::ffi_type_label(ffi_type), + FfiType::Int8 + | FfiType::UInt8 + | FfiType::Int16 + | FfiType::UInt16 + | FfiType::Int32 + | FfiType::UInt32 + | FfiType::Int64 + | FfiType::UInt64 + | FfiType::Float32 + | FfiType::Float64 => format!("{}ByReference", self.ffi_type_label(ffi_type)), + FfiType::RustArcPtr(_) => "PointerByReference".to_owned(), + // JNA structs default to ByReference + FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type), + _ => panic!("{ffi_type:?} by reference is not implemented"), } } - fn ffi_type_label(ffi_type: &FfiType) -> String { + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { // Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not // support them yet. Thus, we use the signed variants to represent both signed and unsigned @@ -327,19 +429,35 @@ impl KotlinCodeOracle { FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), + FfiType::Handle => "Long".to_string(), FfiType::RustArcPtr(_) => "Pointer".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) } + FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(), FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(), - FfiType::ForeignCallback => "ForeignCallback".to_string(), - FfiType::ForeignExecutorHandle => "USize".to_string(), - FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), - FfiType::RustFutureHandle => "Pointer".to_string(), - FfiType::RustFutureContinuationCallback => { - "UniFffiRustFutureContinuationCallbackType".to_string() - } - FfiType::RustFutureContinuationData => "USize".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), + FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner), + FfiType::VoidPointer => "Pointer".to_string(), + } + } + + /// Get the name of the interface and class name for an object. + /// + /// If we support callback interfaces, the interface name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the interface name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the interface. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, ci: &ComponentInterface, obj: &Object) -> (String, String) { + let class_name = self.class_name(ci, obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Interface"), class_name) } } } @@ -376,12 +494,11 @@ impl<T: AsType> AsCodeType for T { Type::Duration => Box::new(miscellany::DurationCodeType), Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), - Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)), Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -401,6 +518,7 @@ impl<T: AsType> AsCodeType for T { mod filters { use super::*; pub use crate::backend::filters::*; + use uniffi_meta::LiteralMetadata; pub(super) fn type_name( as_ct: &impl AsCodeType, @@ -454,8 +572,52 @@ mod filters { Ok(as_ct.as_codetype().literal(literal, ci)) } + // Get the idiomatic Kotlin rendering of an integer. + fn int_literal(t: &Option<Type>, base10: String) -> Result<String, askama::Error> { + if let Some(t) = t { + match t { + Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Ok(base10), + Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Ok(base10 + "u"), + _ => Err(askama::Error::Custom(Box::new(UniFFIError::new( + "Only ints are supported.".to_string(), + )))), + } + } else { + Err(askama::Error::Custom(Box::new(UniFFIError::new( + "Enum hasn't defined a repr".to_string(), + )))) + } + } + + // Get the idiomatic Kotlin rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + match literal { + // Kotlin doesn't convert between signed and unsigned by default + // so we'll need to make sure we define the type as appropriately + LiteralMetadata::UInt(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()), + LiteralMetadata::Int(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()), + _ => Err(askama::Error::Custom(Box::new(UniFFIError::new( + "Only ints are supported.".to_string(), + )))), + } + } + pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> { - Ok(KotlinCodeOracle::ffi_type_label_by_value(type_)) + Ok(KotlinCodeOracle.ffi_type_label_by_value(type_)) + } + + pub fn ffi_type_name_for_ffi_struct(type_: &FfiType) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_)) + } + + pub fn ffi_default_value(type_: FfiType) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_default_value(&type_)) + } + + /// Get the idiomatic Kotlin rendering of a function name. + pub fn class_name(nm: &str, ci: &ComponentInterface) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.class_name(ci, nm)) } /// Get the idiomatic Kotlin rendering of a function name. @@ -468,6 +630,11 @@ mod filters { Ok(KotlinCodeOracle.var_name(nm)) } + /// Get the idiomatic Kotlin rendering of a variable name. + pub fn var_name_raw(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.var_name_raw(nm)) + } + /// Get a String representing the name used for an individual enum variant. pub fn variant_name(v: &Variant) -> Result<String, askama::Error> { Ok(KotlinCodeOracle.enum_variant_name(v.name())) @@ -478,13 +645,30 @@ mod filters { Ok(KotlinCodeOracle.convert_error_suffix(&name)) } + /// Get the idiomatic Kotlin rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_callback_name(nm)) + } + + /// Get the idiomatic Kotlin rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(KotlinCodeOracle.ffi_struct_name(nm)) + } + + pub fn object_names( + obj: &Object, + ci: &ComponentInterface, + ) -> Result<(String, String), askama::Error> { + Ok(KotlinCodeOracle.object_names(ci, obj)) + } + pub fn async_poll( callable: impl Callable, ci: &ComponentInterface, ) -> Result<String, askama::Error> { let ffi_func = callable.ffi_rust_future_poll(ci); Ok(format!( - "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}" + "{{ future, callback, continuation -> UniffiLib.INSTANCE.{ffi_func}(future, callback, continuation) }}" )) } @@ -493,7 +677,7 @@ mod filters { ci: &ComponentInterface, ) -> Result<String, askama::Error> { let ffi_func = callable.ffi_rust_future_complete(ci); - let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)"); + let call = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)"); let call = match callable.return_type() { Some(Type::External { kind: ExternalKind::DataClass, @@ -502,7 +686,7 @@ mod filters { }) => { // Need to convert the RustBuffer from our package to the RustBuffer of the external package let suffix = KotlinCodeOracle.class_name(ci, &name); - format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}") + format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}") } _ => call, }; @@ -515,7 +699,7 @@ mod filters { ) -> Result<String, askama::Error> { let ffi_func = callable.ffi_rust_future_free(ci); Ok(format!( - "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}" + "{{ future -> UniffiLib.INSTANCE.{ffi_func}(future) }}" )) } @@ -527,4 +711,13 @@ mod filters { pub fn unquote(nm: &str) -> Result<String, askama::Error> { Ok(nm.trim_matches('`').to_string()) } + + /// Get the idiomatic Kotlin rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let middle = textwrap::indent(&textwrap::dedent(docstring), " * "); + let wrapped = format!("/**\n{middle}\n */"); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs index c39ae59cce..5a4305d14a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs @@ -3,25 +3,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; -use crate::ComponentInterface; +use crate::{interface::ObjectImpl, ComponentInterface}; #[derive(Debug)] pub struct ObjectCodeType { - id: String, + name: String, + imp: ObjectImpl, } impl ObjectCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(name: String, imp: ObjectImpl) -> Self { + Self { name, imp } } } impl CodeType for ObjectCodeType { fn type_label(&self, ci: &ComponentInterface) -> String { - super::KotlinCodeOracle.class_name(ci, &self.id) + super::KotlinCodeOracle.class_name(ci, &self.name) } fn canonical_name(&self) -> String { - format!("Type{}", self.id) + format!("Type{}", self.name) + } + + fn initialization_fn(&self) -> Option<String> { + self.imp + .has_callback_interface() + .then(|| format!("uniffiCallbackInterface{}.register", self.name)) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs index 22495fa209..0bc5a5d99e 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs @@ -9,7 +9,11 @@ use paste::paste; fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { fn typed_number(type_: &Type, num_str: String) -> String { - match type_ { + let unwrapped_type = match type_ { + Type::Optional { inner_type } => inner_type, + t => t, + }; + match unwrapped_type { // Bytes, Shorts and Ints can all be inferred from the type. Type::Int8 | Type::Int16 | Type::Int32 => num_str, Type::Int64 => format!("{num_str}L"), @@ -19,7 +23,7 @@ fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String { Type::Float32 => format!("{num_str}f"), Type::Float64 => num_str, - _ => panic!("Unexpected literal: {num_str} is not a number"), + _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"), } } @@ -56,7 +60,7 @@ macro_rules! impl_code_type_for_primitive { impl CodeType for $T { fn type_label(&self, _ci: &ComponentInterface) -> String { - $class_name.into() + format!("kotlin.{}", $class_name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index c6a32655f2..b28fbd2c80 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -1,44 +1,117 @@ // Async return type handlers -internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort() -internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort() +internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte() +internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte() -internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>() +internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>() // FFI type for Rust future continuations -internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType { - override fun callback(continuationHandle: USize, pollResult: Short) { - uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult) - } - - internal fun register(lib: _UniFFILib) { - lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this) +internal object uniffiRustFutureContinuationCallbackImpl: UniffiRustFutureContinuationCallback { + override fun callback(data: Long, pollResult: Byte) { + uniffiContinuationHandleMap.remove(data).resume(pollResult) } } internal suspend fun<T, F, E: Exception> uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, USize) -> Unit, - completeFunc: (Pointer, RustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, + rustFuture: Long, + pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit, + completeFunc: (Long, UniffiRustCallStatus) -> F, + freeFunc: (Long) -> Unit, liftFunc: (F) -> T, - errorHandler: CallStatusErrorHandler<E> + errorHandler: UniffiRustCallStatusErrorHandler<E> ): T { try { do { - val pollResult = suspendCancellableCoroutine<Short> { continuation -> + val pollResult = suspendCancellableCoroutine<Byte> { continuation -> pollFunc( rustFuture, + uniffiRustFutureContinuationCallbackImpl, uniffiContinuationHandleMap.insert(continuation) ) } } while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY); return liftFunc( - rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) + uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) }) ) } finally { freeFunc(rustFuture) } } +{%- if ci.has_async_callback_interface_definition() %} +internal inline fun<T> uniffiTraitInterfaceCallAsync( + crossinline makeCall: suspend () -> T, + crossinline handleSuccess: (T) -> Unit, + crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit, +): UniffiForeignFuture { + // Using `GlobalScope` is labeled as a "delicate API" and generally discouraged in Kotlin programs, since it breaks structured concurrency. + // However, our parent task is a Rust future, so we're going to need to break structure concurrency in any case. + // + // Uniffi does its best to support structured concurrency across the FFI. + // If the Rust future is dropped, `uniffiForeignFutureFreeImpl` is called, which will cancel the Kotlin coroutine if it's still running. + @OptIn(DelicateCoroutinesApi::class) + val job = GlobalScope.launch { + try { + handleSuccess(makeCall()) + } catch(e: Exception) { + handleError( + UniffiRustCallStatus.create( + UNIFFI_CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(e.toString()), + ) + ) + } + } + val handle = uniffiForeignFutureHandleMap.insert(job) + return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl) +} + +internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithError( + crossinline makeCall: suspend () -> T, + crossinline handleSuccess: (T) -> Unit, + crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit, + crossinline lowerError: (E) -> RustBuffer.ByValue, +): UniffiForeignFuture { + // See uniffiTraitInterfaceCallAsync for details on `DelicateCoroutinesApi` + @OptIn(DelicateCoroutinesApi::class) + val job = GlobalScope.launch { + try { + handleSuccess(makeCall()) + } catch(e: Exception) { + if (e is E) { + handleError( + UniffiRustCallStatus.create( + UNIFFI_CALL_ERROR, + lowerError(e), + ) + ) + } else { + handleError( + UniffiRustCallStatus.create( + UNIFFI_CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(e.toString()), + ) + ) + } + } + } + val handle = uniffiForeignFutureHandleMap.insert(job) + return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl) +} + +internal val uniffiForeignFutureHandleMap = UniffiHandleMap<Job>() + +internal object uniffiForeignFutureFreeImpl: UniffiForeignFutureFree { + override fun callback(handle: Long) { + val job = uniffiForeignFutureHandleMap.remove(handle) + if (!job.isCompleted) { + job.cancel() + } + } +} + +// For testing +public fun uniffiForeignFutureHandleCount() = uniffiForeignFutureHandleMap.size + +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt index 8cfa2ce000..c6b266066d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt @@ -11,7 +11,7 @@ public object FfiConverterBoolean: FfiConverter<Boolean, Byte> { return if (value) 1.toByte() else 0.toByte() } - override fun allocationSize(value: Boolean) = 1 + override fun allocationSize(value: Boolean) = 1UL override fun write(value: Boolean, buf: ByteBuffer) { buf.put(lower(value)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt index 4840a199b4..c9449069e2 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt @@ -5,8 +5,8 @@ public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> { buf.get(byteArr) return byteArr } - override fun allocationSize(value: ByteArray): Int { - return 4 + value.size + override fun allocationSize(value: ByteArray): ULong { + return 4UL + value.size.toULong() } override fun write(value: ByteArray, buf: ByteBuffer) { buf.putInt(value.size) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt new file mode 100644 index 0000000000..30a39d9afb --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt @@ -0,0 +1,117 @@ +{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %} + +{%- let trait_impl=format!("uniffiCallbackInterface{}", name) %} + +// Put the implementation in an object so we don't pollute the top-level namespace +internal object {{ trait_impl }} { + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + internal object {{ meth.name()|var_name }}: {{ ffi_callback.name()|ffi_callback_name }} { + override fun callback( + {%- for arg in ffi_callback.arguments() -%} + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() -%} + uniffiCallStatus: UniffiRustCallStatus, + {%- endif -%} + ) + {%- match ffi_callback.return_type() %} + {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}, + {%- when None %} + {%- endmatch %} { + val uniffiObj = {{ ffi_converter_name }}.handleMap.get(uniffiHandle) + val makeCall = {% if meth.is_async() %}suspend {% endif %}{ -> + uniffiObj.{{ meth.name()|fn_name() }}( + {%- for arg in meth.arguments() %} + {{ arg|lift_fn }}({{ arg.name()|var_name }}), + {%- endfor %} + ) + } + {%- if !meth.is_async() %} + + {%- match meth.return_type() %} + {%- when Some(return_type) %} + val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) } + {%- when None %} + val writeReturn = { _: Unit -> Unit } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallWithError( + uniffiCallStatus, + makeCall, + writeReturn, + { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) } + ) + {%- endmatch %} + + {%- else %} + val uniffiHandleSuccess = { {% if meth.return_type().is_some() %}returnValue{% else %}_{% endif %}: {% match meth.return_type() %}{%- when Some(return_type) %}{{ return_type|type_name(ci) }}{%- when None %}Unit{% endmatch %} -> + val uniffiResult = {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lower_fn }}(returnValue), + {%- when None %} + {%- endmatch %} + UniffiRustCallStatus.ByValue() + ) + uniffiResult.write() + uniffiFutureCallback.callback(uniffiCallbackData, uniffiResult) + } + val uniffiHandleError = { callStatus: UniffiRustCallStatus.ByValue -> + uniffiFutureCallback.callback( + uniffiCallbackData, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type.into()|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + callStatus, + ), + ) + } + + uniffiOutReturn.uniffiSetValue( + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCallAsync( + makeCall, + uniffiHandleSuccess, + uniffiHandleError + ) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallAsyncWithError( + makeCall, + uniffiHandleSuccess, + uniffiHandleError, + { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) } + ) + {%- endmatch %} + ) + {%- endif %} + } + } + {%- endfor %} + + internal object uniffiFree: {{ "CallbackInterfaceFree"|ffi_callback_name }} { + override fun callback(handle: Long) { + {{ ffi_converter_name }}.handleMap.remove(handle) + } + } + + internal var vtable = {{ vtable|ffi_type_name_by_value }}( + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + {{ meth.name()|var_name() }}, + {%- endfor %} + uniffiFree, + ) + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal fun register(lib: UniffiLib) { + lib.{{ ffi_init_callback.name() }}(vtable) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt index 62a71e02f1..d58a651e24 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -1,43 +1,3 @@ -internal typealias Handle = Long -internal class ConcurrentHandleMap<T>( - private val leftMap: MutableMap<Handle, T> = mutableMapOf(), - private val rightMap: MutableMap<T, Handle> = mutableMapOf() -) { - private val lock = java.util.concurrent.locks.ReentrantLock() - private val currentHandle = AtomicLong(0L) - private val stride = 1L - - fun insert(obj: T): Handle = - lock.withLock { - rightMap[obj] ?: - currentHandle.getAndAdd(stride) - .also { handle -> - leftMap[handle] = obj - rightMap[obj] = handle - } - } - - fun get(handle: Handle) = lock.withLock { - leftMap[handle] - } - - fun delete(handle: Handle) { - this.remove(handle) - } - - fun remove(handle: Handle): T? = - lock.withLock { - leftMap.remove(handle)?.let { obj -> - rightMap.remove(obj) - obj - } - } -} - -interface ForeignCallback : com.sun.jna.Callback { - public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int -} - // Magic number for the Rust proxy to call using the same mechanism as every other method, // to free the callback once it's dropped by Rust. internal const val IDX_CALLBACK_FREE = 0 @@ -46,31 +6,22 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0 internal const val UNIFFI_CALLBACK_ERROR = 1 internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -public abstract class FfiConverterCallbackInterface<CallbackInterface>( - protected val foreignCallback: ForeignCallback -): FfiConverter<CallbackInterface, Handle> { - private val handleMap = ConcurrentHandleMap<CallbackInterface>() - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - internal abstract fun register(lib: _UniFFILib) +public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, Long> { + internal val handleMap = UniffiHandleMap<CallbackInterface>() - fun drop(handle: Handle): RustBuffer.ByValue { - return handleMap.remove(handle).let { RustBuffer.ByValue() } + internal fun drop(handle: Long) { + handleMap.remove(handle) } - override fun lift(value: Handle): CallbackInterface { - return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug") + override fun lift(value: Long): CallbackInterface { + return handleMap.get(value) } override fun read(buf: ByteBuffer) = lift(buf.getLong()) - override fun lower(value: CallbackInterface) = - handleMap.insert(value).also { - assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." } - } + override fun lower(value: CallbackInterface) = handleMap.insert(value) - override fun allocationSize(value: CallbackInterface) = 8 + override fun allocationSize(value: CallbackInterface) = 8UL override fun write(value: CallbackInterface, buf: ByteBuffer) { buf.putLong(lower(value)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt index 5a29f0acc3..d2cdee4f33 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -1,129 +1,13 @@ {%- let cbi = ci|get_callback_interface_definition(name) %} -{%- let type_name = cbi|type_name(ci) %} -{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %} - -{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %} -{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} -{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }} -{{- self.add_import("kotlin.concurrent.withLock") }} - -// Declaration and FfiConverters for {{ type_name }} Callback Interface - -public interface {{ type_name }} { - {% for meth in cbi.methods() -%} - fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) - {%- match meth.return_type() -%} - {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} - {%- else -%} - {%- endmatch %} - {% endfor %} - companion object -} - -// The ForeignCallback that is passed to Rust. -internal class {{ foreign_callback }} : ForeignCallback { - @Suppress("TooGenericExceptionCaught") - override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { - val cb = {{ ffi_converter_name }}.lift(handle) - return when (method) { - IDX_CALLBACK_FREE -> { - {{ ffi_converter_name }}.drop(handle) - // Successful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - UNIFFI_CALLBACK_SUCCESS - } - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - {{ loop.index }} -> { - // Call the method, write to outBuf and return a status code - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info - try { - this.{{ method_name }}(cb, argsData, argsLen, outBuf) - } catch (e: Throwable) { - // Unexpected error - try { - // Try to serialize the error into a string - outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString())) - } catch (e: Throwable) { - // If that fails, then it's time to give up and just return - } - UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - } - {% endfor %} - else -> { - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - try { - // Try to serialize the error into a string - outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index")) - } catch (e: Throwable) { - // If that fails, then it's time to give up and just return - } - UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - } - } - - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name %} - @Suppress("UNUSED_PARAMETER") - private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int { - {%- if meth.arguments().len() > 0 %} - val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also { - it.order(ByteOrder.BIG_ENDIAN) - } - {%- endif %} - - {%- match meth.return_type() %} - {%- when Some with (return_type) %} - fun makeCall() : Int { - val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}( - {%- for arg in meth.arguments() %} - {{ arg|read_fn }}(argsBuf) - {% if !loop.last %}, {% endif %} - {%- endfor %} - ) - outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue)) - return UNIFFI_CALLBACK_SUCCESS - } - {%- when None %} - fun makeCall() : Int { - kotlinCallbackInterface.{{ meth.name()|fn_name }}( - {%- for arg in meth.arguments() %} - {{ arg|read_fn }}(argsBuf) - {%- if !loop.last %}, {% endif %} - {%- endfor %} - ) - return UNIFFI_CALLBACK_SUCCESS - } - {%- endmatch %} - - {%- match meth.throws_type() %} - {%- when None %} - fun makeCallAndHandleError() : Int = makeCall() - {%- when Some(error_type) %} - fun makeCallAndHandleError() : Int = try { - makeCall() - } catch (e: {{ error_type|type_name(ci) }}) { - // Expected error, serialize it into outBuf - outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e)) - UNIFFI_CALLBACK_ERROR - } - {%- endmatch %} - - return makeCallAndHandleError() - } - {% endfor %} -} - -// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. -public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>( - foreignCallback = {{ foreign_callback }}() -) { - override fun register(lib: _UniFFILib) { - rustCall() { status -> - lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status) - } - } -} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} +{%- let interface_name = cbi|type_name(ci) %} +{%- let interface_docstring = cbi.docstring() %} +{%- let methods = cbi.methods() %} +{%- let vtable = cbi.vtable() %} +{%- let vtable_methods = cbi.vtable_methods() %} + +{% include "Interface.kt" %} +{% include "CallbackInterfaceImpl.kt" %} + +// The ffiConverter which transforms the Callbacks in to handles to pass to Rust. +public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt index 04150c5d78..aeb5f58002 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt @@ -49,7 +49,7 @@ public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_nam return {{ config.into_custom.render("builtinValue") }} } - override fun allocationSize(value: {{ name }}): Int { + override fun allocationSize(value: {{ name }}): ULong { val builtinValue = {{ config.from_custom.render("value") }} return {{ builtin|allocation_size_fn }}(builtinValue) } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt index 4237c6f9a8..62e02607f3 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt @@ -14,7 +14,7 @@ public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> { } // 8 bytes for seconds, 4 bytes for nanoseconds - override fun allocationSize(value: java.time.Duration) = 12 + override fun allocationSize(value: java.time.Duration) = 12UL override fun write(value: java.time.Duration, buf: ByteBuffer) { if (value.seconds < 0) { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt index d4c4a1684a..8d1c2235ec 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt @@ -7,12 +7,25 @@ {%- if e.is_flat() %} +{%- call kt::docstring(e, 0) %} +{% match e.variant_discr_type() %} +{% when None %} enum class {{ type_name }} { {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %} {%- endfor %} companion object } +{% when Some with (variant_discr_type) %} +enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) { + {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} + {{ variant|variant_name }}({{ e|variant_discr_literal(loop.index0) }}){% if loop.last %};{% else %},{% endif %} + {%- endfor %} + companion object +} +{% endmatch %} public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer) = try { @@ -21,7 +34,7 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }} throw RuntimeException("invalid enum value, something is very wrong!!", e) } - override fun allocationSize(value: {{ type_name }}) = 4 + override fun allocationSize(value: {{ type_name }}) = 4UL override fun write(value: {{ type_name }}, buf: ByteBuffer) { buf.putInt(value.ordinal + 1) @@ -30,15 +43,18 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }} {% else %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} { {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {% if !variant.has_fields() -%} object {{ variant|type_name(ci) }} : {{ type_name }}() {% else -%} data class {{ variant|type_name(ci) }}( - {% for field in variant.fields() -%} - val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} - {% endfor -%} + {%- for field in variant.fields() -%} + {%- call kt::docstring(field, 8) %} + val {% call kt::field_name(field, loop.index) %}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} + {%- endfor -%} ) : {{ type_name }}() { companion object } @@ -83,9 +99,9 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } is {{ type_name }}.{{ variant|type_name(ci) }} -> { // Add the size for the Int that specifies the variant plus the size needed for all fields ( - 4 + 4UL {%- for field in variant.fields() %} - + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) + + {{ field|allocation_size_fn }}(value.{%- call kt::field_name(field, loop.index) -%}) {%- endfor %} ) } @@ -98,7 +114,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } is {{ type_name }}.{{ variant|type_name(ci) }} -> { buf.putInt({{ loop.index }}) {%- for field in variant.fields() %} - {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {{ field|write_fn }}(value.{%- call kt::field_name(field, loop.index) -%}, buf) {%- endfor %} Unit } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt index 986db5424d..4760c03fd6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt @@ -3,24 +3,26 @@ {%- let canonical_type_name = type_|canonical_name %} {% if e.is_flat() %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} { - // Each variant is a nested class - // Flat enums carries a string error message, so no special implementation is necessary. {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message) {% endfor %} - companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> { override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) } } {%- else %} +{%- call kt::docstring(e, 0) %} sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} { - // Each variant is a nested class {% for variant in e.variants() -%} + {%- call kt::docstring(variant, 4) %} {%- let variant_name = variant|error_variant_name %} class {{ variant_name }}( {% for field in variant.fields() -%} + {%- call kt::docstring(field, 8) %} val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %} {% endfor -%} ) : {{ type_name }}() { @@ -29,7 +31,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di } {% endfor %} - companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> { + companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> { override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf) } @@ -76,15 +78,15 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name } {%- endif %} } - override fun allocationSize(value: {{ type_name }}): Int { + override fun allocationSize(value: {{ type_name }}): ULong { {%- if e.is_flat() %} - return 4 + return 4UL {%- else %} return when(value) { {%- for variant in e.variants() %} is {{ type_name }}.{{ variant|error_variant_name }} -> ( // Add the size for the Int that specifies the variant plus the size needed for all fields - 4 + 4UL {%- for field in variant.fields() %} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}) {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt index 0fade7a0bc..b7e77f0b2d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt @@ -1,5 +1,5 @@ {%- let package_name=self.external_type_package_name(module_path, namespace) %} -{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %} +{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name|class_name(ci)) %} {%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %} {%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %} {%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt index 3b2c9d225a..0de90b9c4b 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt @@ -20,7 +20,7 @@ public interface FfiConverter<KotlinType, FfiType> { // encoding, so we pessimistically allocate the largest size possible (3 // bytes per codepoint). Allocating extra bytes is not really a big deal // because the `RustBuffer` is short-lived. - fun allocationSize(value: KotlinType): Int + fun allocationSize(value: KotlinType): ULong // Write a Kotlin type to a `ByteBuffer` fun write(value: KotlinType, buf: ByteBuffer) @@ -34,11 +34,11 @@ public interface FfiConverter<KotlinType, FfiType> { fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue { val rbuf = RustBuffer.alloc(allocationSize(value)) try { - val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also { + val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also { it.order(ByteOrder.BIG_ENDIAN) } write(value, bbuf) - rbuf.writeField("len", bbuf.position()) + rbuf.writeField("len", bbuf.position().toLong()) return rbuf } catch (e: Throwable) { RustBuffer.free(rbuf) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt index eafec5d122..be91ac8fcb 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterFloat: FfiConverter<Float, Float> { return value } - override fun allocationSize(value: Float) = 4 + override fun allocationSize(value: Float) = 4UL override fun write(value: Float, buf: ByteBuffer) { buf.putFloat(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt index 9fc2892c95..5eb465f0df 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterDouble: FfiConverter<Double, Double> { return value } - override fun allocationSize(value: Double) = 8 + override fun allocationSize(value: Double) = 8UL override fun write(value: Double, buf: ByteBuffer) { buf.putDouble(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt deleted file mode 100644 index 3544b2f9e6..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt +++ /dev/null @@ -1,83 +0,0 @@ -{{ self.add_import("kotlinx.coroutines.CoroutineScope") }} -{{ self.add_import("kotlinx.coroutines.delay") }} -{{ self.add_import("kotlinx.coroutines.isActive") }} -{{ self.add_import("kotlinx.coroutines.launch") }} - -internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte() -internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte() -internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte() -internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte() -internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte() - -// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then -// invokes them. -internal interface UniFfiRustTaskCallback : com.sun.jna.Callback { - fun callback(rustTaskData: Pointer?, statusCode: Byte) -} - -internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback { - fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte { - if (rustTask == null) { - FfiConverterForeignExecutor.drop(handle) - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - } else { - val coroutineScope = FfiConverterForeignExecutor.lift(handle) - if (coroutineScope.isActive) { - val job = coroutineScope.launch { - if (delayMs > 0) { - delay(delayMs.toLong()) - } - rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) - } - job.invokeOnCompletion { cause -> - if (cause != null) { - rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED) - } - } - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - } else { - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED - } - } - } -} - -public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> { - internal val handleMap = UniFfiHandleMap<CoroutineScope>() - - internal fun drop(handle: USize) { - handleMap.remove(handle) - } - - internal fun register(lib: _UniFFILib) { - {%- match ci.ffi_foreign_executor_callback_set() %} - {%- when Some with (fn) %} - lib.{{ fn.name() }}(UniFfiForeignExecutorCallback) - {%- when None %} - {#- No foreign executor, we don't set anything #} - {% endmatch %} - } - - // Number of live handles, exposed so we can test the memory management - public fun handleCount() : Int { - return handleMap.size - } - - override fun allocationSize(value: CoroutineScope) = USize.size - - override fun lift(value: USize): CoroutineScope { - return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift") - } - - override fun read(buf: ByteBuffer): CoroutineScope { - return lift(USize.readFromBuffer(buf)) - } - - override fun lower(value: CoroutineScope): USize { - return handleMap.insert(value) - } - - override fun write(value: CoroutineScope, buf: ByteBuffer) { - lower(value).writeToBuffer(buf) - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt new file mode 100644 index 0000000000..3a56648190 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt @@ -0,0 +1,27 @@ +// Map handles to objects +// +// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. +internal class UniffiHandleMap<T: Any> { + private val map = ConcurrentHashMap<Long, T>() + private val counter = java.util.concurrent.atomic.AtomicLong(0) + + val size: Int + get() = map.size + + // Insert a new object into the handle map and get a handle for it + fun insert(obj: T): Long { + val handle = counter.getAndAdd(1) + map.put(handle, obj) + return handle + } + + // Get an object from the handle map + fun get(handle: Long): T { + return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") + } + + // Remove an entry from the handlemap and get the Kotlin object back + fun remove(handle: Long): T { + return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt index 382a5f7413..1fdbd3ffc0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -1,30 +1,43 @@ // A handful of classes and functions to support the generated data structures. // This would be a good candidate for isolating in its own ffi-support lib. -// Error runtime. + +internal const val UNIFFI_CALL_SUCCESS = 0.toByte() +internal const val UNIFFI_CALL_ERROR = 1.toByte() +internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() + @Structure.FieldOrder("code", "error_buf") -internal open class RustCallStatus : Structure() { +internal open class UniffiRustCallStatus : Structure() { @JvmField var code: Byte = 0 @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() - class ByValue: RustCallStatus(), Structure.ByValue + class ByValue: UniffiRustCallStatus(), Structure.ByValue fun isSuccess(): Boolean { - return code == 0.toByte() + return code == UNIFFI_CALL_SUCCESS } fun isError(): Boolean { - return code == 1.toByte() + return code == UNIFFI_CALL_ERROR } fun isPanic(): Boolean { - return code == 2.toByte() + return code == UNIFFI_CALL_UNEXPECTED_ERROR + } + + companion object { + fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue { + val callStatus = UniffiRustCallStatus.ByValue() + callStatus.code = code + callStatus.error_buf = errorBuf + return callStatus + } } } class InternalException(message: String) : Exception(message) // Each top-level error class has a companion object that can lift the error from the call status's rust buffer -interface CallStatusErrorHandler<E> { +interface UniffiRustCallStatusErrorHandler<E> { fun lift(error_buf: RustBuffer.ByValue): E; } @@ -33,15 +46,15 @@ interface CallStatusErrorHandler<E> { // synchronize itself // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err -private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U { - var status = RustCallStatus(); +private inline fun <U, E: Exception> uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler<E>, callback: (UniffiRustCallStatus) -> U): U { + var status = UniffiRustCallStatus(); val return_value = callback(status) - checkCallStatus(errorHandler, status) + uniffiCheckCallStatus(errorHandler, status) return return_value } -// Check RustCallStatus and throw an error if the call wasn't successful -private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) { +// Check UniffiRustCallStatus and throw an error if the call wasn't successful +private fun<E: Exception> uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler<E>, status: UniffiRustCallStatus) { if (status.isSuccess()) { return } else if (status.isError()) { @@ -60,8 +73,8 @@ private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E } } -// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR -object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> { +// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR +object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler<InternalException> { override fun lift(error_buf: RustBuffer.ByValue): InternalException { RustBuffer.free(error_buf) return InternalException("Unexpected CALL_ERROR") @@ -69,93 +82,38 @@ object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> { } // Call a rust function that returns a plain value -private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U { - return rustCallWithError(NullCallStatusErrorHandler, callback); +private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { + return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback); } -// IntegerType that matches Rust's `usize` / C's `size_t` -public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) { - // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin. - override fun toByte() = toInt().toByte() - // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed. - @Deprecated("`toInt().toChar()` is deprecated") - override fun toChar() = toInt().toChar() - override fun toShort() = toInt().toShort() - - fun writeToBuffer(buf: ByteBuffer) { - // Make sure we always write usize integers using native byte-order, since they may be - // casted to pointer values - buf.order(ByteOrder.nativeOrder()) - try { - when (Native.SIZE_T_SIZE) { - 4 -> buf.putInt(toInt()) - 8 -> buf.putLong(toLong()) - else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") - } - } finally { - buf.order(ByteOrder.BIG_ENDIAN) - } - } - - companion object { - val size: Int - get() = Native.SIZE_T_SIZE - - fun readFromBuffer(buf: ByteBuffer) : USize { - // Make sure we always read usize integers using native byte-order, since they may be - // casted from pointer values - buf.order(ByteOrder.nativeOrder()) - try { - return when (Native.SIZE_T_SIZE) { - 4 -> USize(buf.getInt().toLong()) - 8 -> USize(buf.getLong()) - else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}") - } - } finally { - buf.order(ByteOrder.BIG_ENDIAN) - } - } +internal inline fun<T> uniffiTraitInterfaceCall( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) } } - -// Map handles to objects -// -// This is used when the Rust code expects an opaque pointer to represent some foreign object. -// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an -// object reference , nor does it support leaking a reference to Rust. -// -// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to -// Rust when it needs an opaque pointer. -// -// TODO: refactor callbacks to use this class -internal class UniFfiHandleMap<T: Any> { - private val map = ConcurrentHashMap<USize, T>() - // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible - // values seems like enough. If somehow we generate 4 billion handles, then this will wrap - // around back to zero and we can assume the first handle generated will have been dropped by - // then. - private val counter = java.util.concurrent.atomic.AtomicInteger(0) - - val size: Int - get() = map.size - - fun insert(obj: T): USize { - val handle = USize(counter.getAndAdd(1).toLong()) - map.put(handle, obj) - return handle - } - - fun get(handle: USize): T? { - return map.get(handle) - } - - fun remove(handle: USize): T? { - return map.remove(handle) +internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallWithError( + callStatus: UniffiRustCallStatus, + makeCall: () -> T, + writeReturn: (T) -> Unit, + lowerError: (E) -> RustBuffer.ByValue +) { + try { + writeReturn(makeCall()) + } catch(e: Exception) { + if (e is E) { + callStatus.code = UNIFFI_CALL_ERROR + callStatus.error_buf = lowerError(e) + } else { + callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR + callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) + } } } - -// FFI type for Rust future continuations -internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback { - fun callback(continuationHandle: USize, pollResult: Short); -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt index 75564276be..de8296fff6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterShort: FfiConverter<Short, Short> { return value } - override fun allocationSize(value: Short) = 2 + override fun allocationSize(value: Short) = 2UL override fun write(value: Short, buf: ByteBuffer) { buf.putShort(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt index b7a8131c8b..171809a9c4 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterInt: FfiConverter<Int, Int> { return value } - override fun allocationSize(value: Int) = 4 + override fun allocationSize(value: Int) = 4UL override fun write(value: Int, buf: ByteBuffer) { buf.putInt(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt index 601cfc7c2c..35cf8f3169 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterLong: FfiConverter<Long, Long> { return value } - override fun allocationSize(value: Long) = 8 + override fun allocationSize(value: Long) = 8UL override fun write(value: Long, buf: ByteBuffer) { buf.putLong(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt index 9237768dbf..27c98a6659 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterByte: FfiConverter<Byte, Byte> { return value } - override fun allocationSize(value: Byte) = 1 + override fun allocationSize(value: Byte) = 1UL override fun write(value: Byte, buf: ByteBuffer) { buf.put(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt new file mode 100644 index 0000000000..0b4249fb11 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt @@ -0,0 +1,14 @@ +{%- call kt::docstring_value(interface_docstring, 0) %} +public interface {{ interface_name }} { + {% for meth in methods.iter() -%} + {%- call kt::docstring(meth, 4) %} + {% if meth.is_async() -%}suspend {% endif -%} + fun {{ meth.name()|fn_name }}({% call kt::arg_list(meth, true) %}) + {%- match meth.return_type() -%} + {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} + {%- else -%} + {%- endmatch %} + {% endfor %} + companion object +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt index 776c402727..a80418eb00 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt @@ -12,8 +12,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_n } } - override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int { - val spaceForMapSize = 4 + override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): ULong { + val spaceForMapSize = 4UL val spaceForChildren = value.map { (k, v) -> {{ key_type|allocation_size_fn }}(k) + {{ value_type|allocation_size_fn }}(v) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt index 6a3aeada35..1bac8a435c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt @@ -13,14 +13,57 @@ private inline fun <reified Lib : Library> loadIndirect( return Native.load<Lib>(findLibraryName(componentName), Lib::class.java) } +// Define FFI callback types +{%- for def in ci.ffi_definitions() %} +{%- match def %} +{%- when FfiDefinition::CallbackFunction(callback) %} +internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback { + fun callback( + {%- for arg in callback.arguments() -%} + {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }}, + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() -%} + uniffiCallStatus: UniffiRustCallStatus, + {%- endif -%} + ) + {%- match callback.return_type() %} + {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }} + {%- when None %} + {%- endmatch %} +} +{%- when FfiDefinition::Struct(ffi_struct) %} +@Structure.FieldOrder({% for field in ffi_struct.fields() %}"{{ field.name()|var_name_raw }}"{% if !loop.last %}, {% endif %}{% endfor %}) +internal open class {{ ffi_struct.name()|ffi_struct_name }}( + {%- for field in ffi_struct.fields() %} + @JvmField internal var {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }}, + {%- endfor %} +) : Structure() { + class UniffiByValue( + {%- for field in ffi_struct.fields() %} + {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }}, + {%- endfor %} + ): {{ ffi_struct.name()|ffi_struct_name }}({%- for field in ffi_struct.fields() %}{{ field.name()|var_name }}, {%- endfor %}), Structure.ByValue + + internal fun uniffiSetValue(other: {{ ffi_struct.name()|ffi_struct_name }}) { + {%- for field in ffi_struct.fields() %} + {{ field.name()|var_name }} = other.{{ field.name()|var_name }} + {%- endfor %} + } + +} +{%- when FfiDefinition::Function(_) %} +{# functions are handled below #} +{%- endmatch %} +{%- endfor %} + // A JNA Library to expose the extern-C FFI definitions. // This is an implementation detail which will be called internally by the public API. -internal interface _UniFFILib : Library { +internal interface UniffiLib : Library { companion object { - internal val INSTANCE: _UniFFILib by lazy { - loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}") - .also { lib: _UniFFILib -> + internal val INSTANCE: UniffiLib by lazy { + loadIndirect<UniffiLib>(componentName = "{{ ci.namespace() }}") + .also { lib: UniffiLib -> uniffiCheckContractApiVersion(lib) uniffiCheckApiChecksums(lib) {% for fn in self.initialization_fns() -%} @@ -28,6 +71,12 @@ internal interface _UniFFILib : Library { {% endfor -%} } } + {% if ci.contains_object_types() %} + // The Cleaner for the whole library + internal val CLEANER: UniffiCleaner by lazy { + UniffiCleaner.create() + } + {%- endif %} } {% for func in ci.iter_ffi_function_definitions() -%} @@ -37,7 +86,7 @@ internal interface _UniFFILib : Library { {% endfor %} } -private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { +private fun uniffiCheckContractApiVersion(lib: UniffiLib) { // Get the bindings contract version from our ComponentInterface val bindings_contract_version = {{ ci.uniffi_contract_version() }} // Get the scaffolding contract version by calling the into the dylib @@ -48,7 +97,7 @@ private fun uniffiCheckContractApiVersion(lib: _UniFFILib) { } @Suppress("UNUSED_PARAMETER") -private fun uniffiCheckApiChecksums(lib: _UniFFILib) { +private fun uniffiCheckApiChecksums(lib: UniffiLib) { {%- for (name, expected_checksum) in ci.iter_checksums() %} if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt new file mode 100644 index 0000000000..e3e85544d7 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt @@ -0,0 +1,40 @@ + +// The cleaner interface for Object finalization code to run. +// This is the entry point to any implementation that we're using. +// +// The cleaner registers objects and returns cleanables, so now we are +// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the +// different implmentations available at compile time. +interface UniffiCleaner { + interface Cleanable { + fun clean() + } + + fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable + + companion object +} + +// The fallback Jna cleaner, which is available for both Android, and the JVM. +private class UniffiJnaCleaner : UniffiCleaner { + private val cleaner = com.sun.jna.internal.Cleaner.getCleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + UniffiJnaCleanable(cleaner.register(value, cleanUpTask)) +} + +private class UniffiJnaCleanable( + private val cleanable: com.sun.jna.internal.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} + +// We decide at uniffi binding generation time whether we were +// using Android or not. +// There are further runtime checks to chose the correct implementation +// of the cleaner. +{% if config.android_cleaner() %} +{%- include "ObjectCleanerHelperAndroid.kt" %} +{%- else %} +{%- include "ObjectCleanerHelperJvm.kt" %} +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt new file mode 100644 index 0000000000..d025879848 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt @@ -0,0 +1,26 @@ +{{- self.add_import("android.os.Build") }} +{{- self.add_import("androidx.annotation.RequiresApi") }} + +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + AndroidSystemCleaner() + } else { + UniffiJnaCleaner() + } + +// The SystemCleaner, available from API Level 33. +// Some API Level 33 OSes do not support using it, so we require API Level 34. +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +private class AndroidSystemCleaner : UniffiCleaner { + val cleaner = android.system.SystemCleaner.cleaner() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + AndroidSystemCleanable(cleaner.register(value, cleanUpTask)) +} + +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +private class AndroidSystemCleanable( + private val cleanable: java.lang.ref.Cleaner.Cleanable, +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt new file mode 100644 index 0000000000..c43bc167fc --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt @@ -0,0 +1,25 @@ +private fun UniffiCleaner.Companion.create(): UniffiCleaner = + try { + // For safety's sake: if the library hasn't been run in android_cleaner = true + // mode, but is being run on Android, then we still need to think about + // Android API versions. + // So we check if java.lang.ref.Cleaner is there, and use that… + java.lang.Class.forName("java.lang.ref.Cleaner") + JavaLangRefCleaner() + } catch (e: ClassNotFoundException) { + // … otherwise, fallback to the JNA cleaner. + UniffiJnaCleaner() + } + +private class JavaLangRefCleaner : UniffiCleaner { + val cleaner = java.lang.ref.Cleaner.create() + + override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable = + JavaLangRefCleanable(cleaner.register(value, cleanUpTask)) +} + +private class JavaLangRefCleanable( + val cleanable: java.lang.ref.Cleaner.Cleanable +) : UniffiCleaner.Cleanable { + override fun clean() = cleanable.clean() +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt deleted file mode 100644 index b9352c690f..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt +++ /dev/null @@ -1,161 +0,0 @@ -// Interface implemented by anything that can contain an object reference. -// -// Such types expose a `destroy()` method that must be called to cleanly -// dispose of the contained objects. Failure to call this method may result -// in memory leaks. -// -// The easiest way to ensure this method is called is to use the `.use` -// helper method to execute a block and destroy the object at the end. -interface Disposable { - fun destroy() - companion object { - fun destroy(vararg args: Any?) { - args.filterIsInstance<Disposable>() - .forEach(Disposable::destroy) - } - } -} - -inline fun <T : Disposable?, R> T.use(block: (T) -> R) = - try { - block(this) - } finally { - try { - // N.B. our implementation is on the nullable type `Disposable?`. - this?.destroy() - } catch (e: Throwable) { - // swallow - } - } - -// The base class for all UniFFI Object types. -// -// This class provides core operations for working with the Rust `Arc<T>` pointer to -// the live Rust struct on the other side of the FFI. -// -// There's some subtlety here, because we have to be careful not to operate on a Rust -// struct after it has been dropped, and because we must expose a public API for freeing -// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: -// -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to -// the Rust FFI. -// -// * When an `FFIObject` is no longer needed, its pointer should be passed to a -// special destructor function provided by the Rust FFI, which will drop the -// underlying Rust struct. -// -// * Given an `FFIObject` instance, calling code is expected to call the special -// `destroy` method in order to free it after use, either by calling it explicitly -// or by using a higher-level helper like the `use` method. Failing to do so will -// leak the underlying Rust struct. -// -// * We can't assume that calling code will do the right thing, and must be prepared -// to handle Kotlin method calls executing concurrently with or even after a call to -// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. -// -// * We must never allow Rust code to operate on the underlying Rust struct after -// the destructor has been called, and must never call the destructor more than once. -// Doing so may trigger memory unsafety. -// -// If we try to implement this with mutual exclusion on access to the pointer, there is the -// possibility of a race between a method call and a concurrent call to `destroy`: -// -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. -// * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering -// a use-after-free. -// -// One possible solution would be to use a `ReadWriteLock`, with each method call taking -// a read lock (and thus allowed to run concurrently) and the special `destroy` method -// taking a write lock (and thus blocking on live method calls). However, we aim not to -// generate methods with any hidden blocking semantics, and a `destroy` method that might -// block if called incorrectly seems to meet that bar. -// -// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track -// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` -// has been called. These are updated according to the following rules: -// -// * The initial value of the counter is 1, indicating a live object with no in-flight calls. -// The initial value for the flag is false. -// -// * At the start of each method call, we atomically check the counter. -// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. -// If it is nonzero them we atomically increment it by 1 and proceed with the method call. -// -// * At the end of each method call, we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// * When `destroy` is called, we atomically flip the flag from false to true. -// If the flag was already true we silently fail. -// Otherwise we atomically decrement and check the counter. -// If it has reached zero then we destroy the underlying Rust struct. -// -// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works, -// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. -// -// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been -// called *and* all in-flight method calls have completed, avoiding violating any of the expectations -// of the underlying Rust code. -// -// In the future we may be able to replace some of this with automatic finalization logic, such as using -// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is -// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also -// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1], -// so there would still be some complexity here). -// -// Sigh...all of this for want of a robust finalization mechanism. -// -// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 -// -abstract class FFIObject( - protected val pointer: Pointer -): Disposable, AutoCloseable { - - private val wasDestroyed = AtomicBoolean(false) - private val callCounter = AtomicLong(1) - - open protected fun freeRustArcPtr() { - // To be overridden in subclasses. - } - - override fun destroy() { - // Only allow a single call to this method. - // TODO: maybe we should log a warning if called more than once? - if (this.wasDestroyed.compareAndSet(false, true)) { - // This decrement always matches the initial count of 1 given at creation time. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } - - @Synchronized - override fun close() { - this.destroy() - } - - internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R { - // Check and increment the call counter, to keep the object alive. - // This needs a compare-and-set retry loop in case of concurrent updates. - do { - val c = this.callCounter.get() - if (c == 0L) { - throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") - } - if (c == Long.MAX_VALUE) { - throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") - } - } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. - try { - return block(this.pointer) - } finally { - // This decrement always matches the increment we performed above. - if (this.callCounter.decrementAndGet() == 0L) { - this.freeRustArcPtr() - } - } - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index 8ce27a5d04..62cac7a4d0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -1,125 +1,282 @@ +// This template implements a class for working with a Rust struct via a Pointer/Arc<T> +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc<T>` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + +{{ self.add_import("java.util.concurrent.atomic.AtomicBoolean") }} +{%- if self.include_once_check("interface-support") %} + {%- include "ObjectCleanerHelper.kt" %} +{%- endif %} + {%- let obj = ci|get_object_definition(name) %} -{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %} -{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }} -{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }} +{%- let (interface_name, impl_class_name) = obj|object_names(ci) %} +{%- let methods = obj.methods() %} +{%- let interface_docstring = obj.docstring() %} +{%- let is_error = ci.is_name_used_as_error(name) %} +{%- let ffi_converter_name = obj|ffi_converter_name %} -public interface {{ type_name }}Interface { - {% for meth in obj.methods() -%} - {%- match meth.throws_type() -%} - {%- when Some with (throwable) -%} - @Throws({{ throwable|type_name(ci) }}::class) - {%- when None -%} - {%- endmatch %} - {% if meth.is_async() -%} - suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) - {%- else -%} - fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %}) - {%- endif %} - {%- match meth.return_type() -%} - {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}} - {%- when None -%} - {%- endmatch -%} +{%- include "Interface.kt" %} - {% endfor %} - companion object -} +{%- call kt::docstring(obj, 0) %} +{% if (is_error) %} +open class {{ impl_class_name }} : Exception, Disposable, AutoCloseable, {{ interface_name }} { +{% else -%} +open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }} { +{%- endif %} -class {{ type_name }}( - pointer: Pointer -) : FFIObject(pointer), {{ type_name }}Interface { + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } {%- match obj.primary_constructor() %} - {%- when Some with (cons) %} - constructor({% call kt::arg_list_decl(cons) -%}) : + {%- when Some(cons) %} + {%- if cons.is_async() %} + // Note no constructor generated for this object as it is async. + {%- else %} + {%- call kt::docstring(cons, 4) %} + constructor({% call kt::arg_list(cons, true) -%}) : this({% call kt::to_ffi_call(cons) %}) + {%- endif %} {%- when None %} {%- endmatch %} - /** - * Disconnect the object from the underlying Rust object. - * - * It can be called more than once, but once called, interacting with the object - * causes an `IllegalStateException`. - * - * Clients **must** call this method once done with the object, or cause a memory leak. - */ - override protected fun freeRustArcPtr() { - rustCall() { status -> - _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status) + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } } } - {% for meth in obj.methods() -%} - {%- match meth.throws_type() -%} - {%- when Some with (throwable) %} - @Throws({{ throwable|type_name(ci) }}::class) - {%- else -%} - {%- endmatch -%} - {%- if meth.is_async() %} - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { - return uniffiRustCallAsync( - callWithPointer { thisPtr -> - _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}( - thisPtr, - {% call kt::arg_list_lowered(meth) %} - ) - }, - {{ meth|async_poll(ci) }}, - {{ meth|async_complete(ci) }}, - {{ meth|async_free(ci) }}, - // lift function - {%- match meth.return_type() %} - {%- when Some(return_type) %} - { {{ return_type|lift_fn }}(it) }, - {%- when None %} - { Unit }, - {% endmatch %} - // Error FFI converter - {%- match meth.throws_type() %} - {%- when Some(e) %} - {{ e|type_name(ci) }}.ErrorHandler, - {%- when None %} - NullCallStatusErrorHandler, - {%- endmatch %} - ) - } - {%- else -%} - {%- match meth.return_type() -%} - {%- when Some with (return_type) -%} - override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} = - callWithPointer { - {%- call kt::to_ffi_call_with_prefix("it", meth) %} - }.let { - {{ return_type|lift_fn }}(it) + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (! this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } } + } - {%- when None -%} - override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) = - callWithPointer { - {%- call kt::to_ffi_call_with_prefix("it", meth) %} + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status) + } + } } - {% endmatch %} - {% endif %} + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.{{ obj.ffi_object_clone().name() }}(pointer!!, status) + } + } + + {% for meth in obj.methods() -%} + {%- call kt::func_decl("override", meth, 4) %} {% endfor %} + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {% when UniffiTrait::Display { fmt } %} + override fun toString(): String { + return {{ fmt.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(fmt) %}) + } + {% when UniffiTrait::Eq { eq, ne } %} + {# only equals used #} + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is {{ impl_class_name}}) return false + return {{ eq.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(eq) %}) + } + {% when UniffiTrait::Hash { hash } %} + override fun hashCode(): Int { + return {{ hash.return_type().unwrap()|lift_fn }}({%- call kt::to_ffi_call(hash) %}).toInt() + } + {%- else %} + {%- endmatch %} + {%- endfor %} + + {# XXX - "companion object" confusion? How to have alternate constructors *and* be an error? #} {% if !obj.alternate_constructors().is_empty() -%} companion object { {% for cons in obj.alternate_constructors() -%} - fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} = - {{ type_name }}({% call kt::to_ffi_call(cons) %}) + {% call kt::func_decl("", cons, 4) %} {% endfor %} } + {% else if is_error %} + companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ impl_class_name }}> { + override fun lift(error_buf: RustBuffer.ByValue): {{ impl_class_name }} { + // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer. + val bb = error_buf.asByteBuffer() + if (bb == null) { + throw InternalException("?") + } + return {{ ffi_converter_name }}.read(bb) + } + } {% else %} companion object {% endif %} } -public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { - override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it } +{%- if obj.has_callback_interface() %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{% include "CallbackInterfaceImpl.kt" %} +{%- endif %} + +public object {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { + {%- if obj.has_callback_interface() %} + internal val handleMap = UniffiHandleMap<{{ type_name }}>() + {%- endif %} + + override fun lower(value: {{ type_name }}): Pointer { + {%- if obj.has_callback_interface() %} + return Pointer(handleMap.insert(value)) + {%- else %} + return value.uniffiClonePointer() + {%- endif %} + } override fun lift(value: Pointer): {{ type_name }} { - return {{ type_name }}(value) + return {{ impl_class_name }}(value) } override fun read(buf: ByteBuffer): {{ type_name }} { @@ -128,7 +285,7 @@ public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointe return lift(Pointer(buf.getLong())) } - override fun allocationSize(value: {{ type_name }}) = 8 + override fun allocationSize(value: {{ type_name }}) = 8UL override fun write(value: {{ type_name }}, buf: ByteBuffer) { // The Rust code always expects pointers written as 8 bytes, diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt index 56cb5f87a5..98451e1451 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt @@ -8,11 +8,11 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam return {{ inner_type|read_fn }}(buf) } - override fun allocationSize(value: {{ inner_type_name }}?): Int { + override fun allocationSize(value: {{ inner_type_name }}?): ULong { if (value == null) { - return 1 + return 1UL } else { - return 1 + {{ inner_type|allocation_size_fn }}(value) + return 1UL + {{ inner_type|allocation_size_fn }}(value) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md new file mode 100644 index 0000000000..0e770cb829 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md @@ -0,0 +1,13 @@ +# Rules for the Kotlin template code + +## Naming + +Private variables, classes, functions, etc. should be prefixed with `uniffi`, `Uniffi`, or `UNIFFI`. +This avoids naming collisions with user-defined items. +Users will not get name collisions as long as they don't use "uniffi", which is reserved for us. + +In particular, make sure to use the `uniffi` prefix for any variable names in generated functions. +If you name a variable something like `result` the code will probably work initially. +Then it will break later on when a user decides to define a function with a parameter named `result`. + +Note: this doesn't apply to items that we want to expose, for example users may want to catch `InternalException` so doesn't get the `Uniffi` prefix. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt index b588ca1398..bc3028c736 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt @@ -1,8 +1,11 @@ {%- let rec = ci|get_record_definition(name) %} +{%- if rec.has_fields() %} +{%- call kt::docstring(rec, 0) %} data class {{ type_name }} ( {%- for field in rec.fields() %} - var {{ field.name()|var_name }}: {{ field|type_name(ci) -}} + {%- call kt::docstring(field, 4) %} + {% if config.generate_immutable_records() %}val{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name(ci) -}} {%- match field.default_value() %} {%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }} {%- else %} @@ -18,21 +21,39 @@ data class {{ type_name }} ( {% endif %} companion object } +{%- else -%} +{%- call kt::docstring(rec, 0) %} +class {{ type_name }} { + override fun equals(other: Any?): Boolean { + return other is {{ type_name }} + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + companion object +} +{%- endif %} public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> { override fun read(buf: ByteBuffer): {{ type_name }} { + {%- if rec.has_fields() %} return {{ type_name }}( {%- for field in rec.fields() %} {{ field|read_fn }}(buf), {%- endfor %} ) + {%- else %} + return {{ type_name }}() + {%- endif %} } - override fun allocationSize(value: {{ type_name }}) = ( + override fun allocationSize(value: {{ type_name }}) = {%- if rec.has_fields() %} ( {%- for field in rec.fields() %} - {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%} + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %} {%- endfor %} - ) + ) {%- else %} 0UL {%- endif %} override fun write(value: {{ type_name }}, buf: ByteBuffer) { {%- for field in rec.fields() %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt index dfbea24074..b28f25bfc3 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt @@ -4,32 +4,41 @@ @Structure.FieldOrder("capacity", "len", "data") open class RustBuffer : Structure() { - @JvmField var capacity: Int = 0 - @JvmField var len: Int = 0 + // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values. + // When dealing with these fields, make sure to call `toULong()`. + @JvmField var capacity: Long = 0 + @JvmField var len: Long = 0 @JvmField var data: Pointer? = null class ByValue: RustBuffer(), Structure.ByValue class ByReference: RustBuffer(), Structure.ByReference + internal fun setValue(other: RustBuffer) { + capacity = other.capacity + len = other.len + data = other.data + } + companion object { - internal fun alloc(size: Int = 0) = rustCall() { status -> - _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status) + internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status -> + // Note: need to convert the size to a `Long` value to make this work with JVM. + UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), status) }.also { if(it.data == null) { throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})") } } - internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue { + internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue { var buf = RustBuffer.ByValue() - buf.capacity = capacity - buf.len = len + buf.capacity = capacity.toLong() + buf.len = len.toLong() buf.data = data return buf } - internal fun free(buf: RustBuffer.ByValue) = rustCall() { status -> - _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status) + internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status -> + UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status) } } @@ -53,9 +62,9 @@ class RustBufferByReference : ByReference(16) { fun setValue(value: RustBuffer.ByValue) { // NOTE: The offsets are as they are in the C-like struct. val pointer = getPointer() - pointer.setInt(0, value.capacity) - pointer.setInt(4, value.len) - pointer.setPointer(8, value.data) + pointer.setLong(0, value.capacity) + pointer.setLong(8, value.len) + pointer.setPointer(16, value.data) } /** @@ -64,9 +73,9 @@ class RustBufferByReference : ByReference(16) { fun getValue(): RustBuffer.ByValue { val pointer = getPointer() val value = RustBuffer.ByValue() - value.writeField("capacity", pointer.getInt(0)) - value.writeField("len", pointer.getInt(4)) - value.writeField("data", pointer.getPointer(8)) + value.writeField("capacity", pointer.getLong(0)) + value.writeField("len", pointer.getLong(8)) + value.writeField("data", pointer.getLong(16)) return value } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt index 876d1bc05e..61f911cb0c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt @@ -8,15 +8,15 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_typ } } - override fun allocationSize(value: List<{{ inner_type_name }}>): Int { - val sizeForLength = 4 + override fun allocationSize(value: List<{{ inner_type_name }}>): ULong { + val sizeForLength = 4UL val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum() return sizeForLength + sizeForItems } override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) { buf.putInt(value.size) - value.forEach { + value.iterator().forEach { {{ inner_type|write_fn }}(it, buf) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt index 68324be4f9..b67435bd1a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt @@ -4,7 +4,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { // store our length and avoid writing it out to the buffer. override fun lift(value: RustBuffer.ByValue): String { try { - val byteArr = ByteArray(value.len) + val byteArr = ByteArray(value.len.toInt()) value.asByteBuffer()!!.get(byteArr) return byteArr.toString(Charsets.UTF_8) } finally { @@ -31,7 +31,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { val byteBuf = toUtf8(value) // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. - val rbuf = RustBuffer.alloc(byteBuf.limit()) + val rbuf = RustBuffer.alloc(byteBuf.limit().toULong()) rbuf.asByteBuffer()!!.put(byteBuf) return rbuf } @@ -39,9 +39,9 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> { // We aren't sure exactly how many bytes our string will be once it's UTF-8 // encoded. Allocate 3 bytes per UTF-16 code unit which will always be // enough. - override fun allocationSize(value: String): Int { - val sizeForLength = 4 - val sizeForString = value.length * 3 + override fun allocationSize(value: String): ULong { + val sizeForLength = 4UL + val sizeForString = value.length.toULong() * 3UL return sizeForLength + sizeForString } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt index 21069d7ce8..10a450a4bd 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt @@ -14,7 +14,7 @@ public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> { } // 8 bytes for seconds, 4 bytes for nanoseconds - override fun allocationSize(value: java.time.Instant) = 12 + override fun allocationSize(value: java.time.Instant) = 12UL override fun write(value: java.time.Instant, buf: ByteBuffer) { var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt index 6a841d3484..681c48093a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt @@ -1,51 +1 @@ -{%- if func.is_async() %} -{%- match func.throws_type() -%} -{%- when Some with (throwable) %} -@Throws({{ throwable|type_name(ci) }}::class) -{%- else -%} -{%- endmatch %} - -@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { - return uniffiRustCallAsync( - _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}), - {{ func|async_poll(ci) }}, - {{ func|async_complete(ci) }}, - {{ func|async_free(ci) }}, - // lift function - {%- match func.return_type() %} - {%- when Some(return_type) %} - { {{ return_type|lift_fn }}(it) }, - {%- when None %} - { Unit }, - {% endmatch %} - // Error FFI converter - {%- match func.throws_type() %} - {%- when Some(e) %} - {{ e|type_name(ci) }}.ErrorHandler, - {%- when None %} - NullCallStatusErrorHandler, - {%- endmatch %} - ) -} - -{%- else %} -{%- match func.throws_type() -%} -{%- when Some with (throwable) %} -@Throws({{ throwable|type_name(ci) }}::class) -{%- else -%} -{%- endmatch -%} - -{%- match func.return_type() -%} -{%- when Some with (return_type) %} - -fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} { - return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %}) -} -{% when None %} - -fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) = - {% call kt::to_ffi_call(func) %} - -{% endmatch %} -{%- endif %} +{%- call kt::func_decl("", func, 8) %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt index 103d444ea3..c27121b701 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt @@ -1,5 +1,38 @@ {%- import "macros.kt" as kt %} +// Interface implemented by anything that can contain an object reference. +// +// Such types expose a `destroy()` method that must be called to cleanly +// dispose of the contained objects. Failure to call this method may result +// in memory leaks. +// +// The easiest way to ensure this method is called is to use the `.use` +// helper method to execute a block and destroy the object at the end. +interface Disposable { + fun destroy() + companion object { + fun destroy(vararg args: Any?) { + args.filterIsInstance<Disposable>() + .forEach(Disposable::destroy) + } + } +} + +inline fun <T : Disposable?, R> T.use(block: (T) -> R) = + try { + block(this) + } finally { + try { + // N.B. our implementation is on the nullable type `Disposable?`. + this?.destroy() + } catch (e: Throwable) { + // swallow + } + } + +/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */ +object NoPointer + {%- for type_ in ci.iter_types() %} {%- let type_name = type_|type_name(ci) %} {%- let ffi_converter_name = type_|ffi_converter_name %} @@ -82,9 +115,6 @@ {%- when Type::CallbackInterface { module_path, name } %} {% include "CallbackInterfaceTemplate.kt" %} -{%- when Type::ForeignExecutor %} -{% include "ForeignExecutorTemplate.kt" %} - {%- when Type::Timestamp %} {% include "TimestampHelper.kt" %} @@ -104,6 +134,10 @@ {%- if ci.has_async_fns() %} {# Import types needed for async support #} {{ self.add_import("kotlin.coroutines.resume") }} +{{ self.add_import("kotlinx.coroutines.launch") }} {{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }} {{ self.add_import("kotlinx.coroutines.CancellableContinuation") }} +{{ self.add_import("kotlinx.coroutines.DelicateCoroutinesApi") }} +{{ self.add_import("kotlinx.coroutines.Job") }} +{{ self.add_import("kotlinx.coroutines.GlobalScope") }} {%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt index 279a8fa91b..b179145b62 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterUShort: FfiConverter<UShort, Short> { return value.toShort() } - override fun allocationSize(value: UShort) = 2 + override fun allocationSize(value: UShort) = 2UL override fun write(value: UShort, buf: ByteBuffer) { buf.putShort(value.toShort()) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt index da7b5b28d6..202d5bcd5b 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterUInt: FfiConverter<UInt, Int> { return value.toInt() } - override fun allocationSize(value: UInt) = 4 + override fun allocationSize(value: UInt) = 4UL override fun write(value: UInt, buf: ByteBuffer) { buf.putInt(value.toInt()) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt index 44d27ad36b..9be2a5a69d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterULong: FfiConverter<ULong, Long> { return value.toLong() } - override fun allocationSize(value: ULong) = 8 + override fun allocationSize(value: ULong) = 8UL override fun write(value: ULong, buf: ByteBuffer) { buf.putLong(value.toLong()) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt index b6d176603e..ee360673e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt @@ -11,7 +11,7 @@ public object FfiConverterUByte: FfiConverter<UByte, Byte> { return value.toByte() } - override fun allocationSize(value: UByte) = 1 + override fun allocationSize(value: UByte) = 1UL override fun write(value: UByte, buf: ByteBuffer) { buf.put(value.toByte()) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt index 6a95d6a66d..7acfdc8861 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt @@ -1,32 +1,91 @@ {# // Template to call into rust. Used in several places. -// Variable names in `arg_list_decl` should match up with arg lists +// Variable names in `arg_list` should match up with arg lists // passed to rust via `arg_list_lowered` #} {%- macro to_ffi_call(func) -%} - {%- match func.throws_type() %} - {%- when Some with (e) %} - rustCallWithError({{ e|type_name(ci) }}) - {%- else %} - rustCall() - {%- endmatch %} { _status -> - _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status) -} -{%- endmacro -%} + {%- if func.takes_self() %} + callWithPointer { + {%- call to_raw_ffi_call(func) %} + } + {% else %} + {%- call to_raw_ffi_call(func) %} + {% endif %} +{%- endmacro %} -{%- macro to_ffi_call_with_prefix(prefix, func) %} +{%- macro to_raw_ffi_call(func) -%} {%- match func.throws_type() %} {%- when Some with (e) %} - rustCallWithError({{ e|type_name(ci) }}) + uniffiRustCallWithError({{ e|type_name(ci) }}) {%- else %} - rustCall() + uniffiRustCall() {%- endmatch %} { _status -> - _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}( - {{- prefix }}, - {% call arg_list_lowered(func) %} + UniffiLib.INSTANCE.{{ func.ffi_func().name() }}( + {% if func.takes_self() %}it, {% endif -%} + {% call arg_list_lowered(func) -%} _status) } +{%- endmacro -%} + +{%- macro func_decl(func_decl, callable, indent) %} + {%- call docstring(callable, indent) %} + {%- match callable.throws_type() -%} + {%- when Some(throwable) %} + @Throws({{ throwable|type_name(ci) }}::class) + {%- else -%} + {%- endmatch -%} + {%- if callable.is_async() %} + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + {{ func_decl }} suspend fun {{ callable.name()|fn_name }}( + {%- call arg_list(callable, !callable.takes_self()) -%} + ){% match callable.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} { + return {% call call_async(callable) %} + } + {%- else -%} + {{ func_decl }} fun {{ callable.name()|fn_name }}( + {%- call arg_list(callable, !callable.takes_self()) -%} + ){%- match callable.return_type() -%} + {%- when Some with (return_type) -%} + : {{ return_type|type_name(ci) }} { + return {{ return_type|lift_fn }}({% call to_ffi_call(callable) %}) + } + {%- when None %} + = {% call to_ffi_call(callable) %} + {%- endmatch %} + {% endif %} +{% endmacro %} + +{%- macro call_async(callable) -%} + uniffiRustCallAsync( +{%- if callable.takes_self() %} + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}( + thisPtr, + {% call arg_list_lowered(callable) %} + ) + }, +{%- else %} + UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}), +{%- endif %} + {{ callable|async_poll(ci) }}, + {{ callable|async_complete(ci) }}, + {{ callable|async_free(ci) }}, + // lift function + {%- match callable.return_type() %} + {%- when Some(return_type) %} + { {{ return_type|lift_fn }}(it) }, + {%- when None %} + { Unit }, + {% endmatch %} + // Error FFI converter + {%- match callable.throws_type() %} + {%- when Some(e) %} + {{ e|type_name(ci) }}.ErrorHandler, + {%- when None %} + UniffiNullRustCallStatusErrorHandler, + {%- endmatch %} + ) {%- endmacro %} {%- macro arg_list_lowered(func) %} @@ -37,37 +96,42 @@ {#- // Arglist as used in kotlin declarations of methods, functions and constructors. +// If is_decl, then default values be specified. // Note the var_name and type_name filters. -#} -{% macro arg_list_decl(func) %} - {%- for arg in func.arguments() -%} +{% macro arg_list(func, is_decl) %} +{%- for arg in func.arguments() -%} {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} - {%- match arg.default_value() %} - {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} - {%- else %} - {%- endmatch %} - {%- if !loop.last %}, {% endif -%} - {%- endfor %} +{%- if is_decl %} +{%- match arg.default_value() %} +{%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }} +{%- else %} +{%- endmatch %} +{%- endif %} +{%- if !loop.last %}, {% endif -%} +{%- endfor %} {%- endmacro %} -{% macro arg_list_protocol(func) %} - {%- for arg in func.arguments() -%} - {{ arg.name()|var_name }}: {{ arg|type_name(ci) }} - {%- if !loop.last %}, {% endif -%} - {%- endfor %} -{%- endmacro %} {#- -// Arglist as used in the _UniFFILib function declarations. +// Arglist as used in the UniffiLib function declarations. // Note unfiltered name but ffi_type_name filters. -#} {%- macro arg_list_ffi_decl(func) %} {%- for arg in func.arguments() %} {{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}}, {%- endfor %} - {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %} + {%- if func.has_rust_call_status_arg() %}uniffi_out_err: UniffiRustCallStatus, {% endif %} {%- endmacro -%} +{% macro field_name(field, field_num) %} +{%- if field.name().is_empty() -%} +v{{- field_num -}} +{%- else -%} +{{ field.name()|var_name }} +{%- endif -%} +{%- endmacro %} + // Macro for destroying fields {%- macro destroy_fields(member) %} Disposable.destroy( @@ -75,3 +139,15 @@ this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%} {% endfor -%}) {%- endmacro -%} + +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt index 9ee4229018..2cdc72a5e2 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt @@ -1,6 +1,8 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! +{%- call kt::docstring_value(ci.namespace_docstring(), 0) %} + @file:Suppress("NAME_SHADOWING") package {{ config.package_name() }}; @@ -28,6 +30,7 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.CharBuffer import java.nio.charset.CodingErrorAction +import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.ConcurrentHashMap {%- for req in self.imports() %} @@ -37,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap {% include "RustBufferTemplate.kt" %} {% include "FfiConverterTemplate.kt" %} {% include "Helpers.kt" %} +{% include "HandleMap.kt" %} // Contains loading, initialization code, // and the FFI Function declarations in a com.sun.jna.Library. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs index 7b78540741..0824015751 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs @@ -2,10 +2,8 @@ License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::{ - bindings::{RunScriptOptions, TargetLanguage}, - library_mode::generate_bindings, -}; +use crate::bindings::TargetLanguage; +use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use std::env; @@ -33,14 +31,18 @@ pub fn run_script( args: Vec<String>, options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file); + let script_path = Utf8Path::new(script_file); let test_helper = UniFFITestHelper::new(crate_name)?; - let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; + let out_dir = test_helper.create_out_dir(tmp_dir, script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; generate_bindings( &cdylib_path, None, - &[TargetLanguage::Kotlin], + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Kotlin], + try_format_code: false, + }, + None, &out_dir, false, )?; diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs index b91bcbe18f..16adeca9a5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs @@ -3,7 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; -use crate::backend::{Literal, Type}; +use crate::{ + backend::{Literal, Type}, + bindings::python::gen_python::AsCodeType, +}; #[derive(Debug)] pub struct OptionalCodeType { @@ -33,8 +36,9 @@ impl CodeType for OptionalCodeType { fn literal(&self, literal: &Literal) -> String { match literal { - Literal::Null => "None".into(), - _ => super::PythonCodeOracle.find(&self.inner).literal(literal), + Literal::None => "None".into(), + Literal::Some { inner } => super::PythonCodeOracle.find(&self.inner).literal(inner), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } } @@ -88,7 +92,11 @@ impl MapCodeType { impl CodeType for MapCodeType { fn type_label(&self) -> String { - "dict".to_string() + format!( + "dict[{}, {}]", + self.key.as_codetype().type_label(), + self.value.as_codetype().type_label() + ) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs deleted file mode 100644 index be3ba1d791..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use super::CodeType; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { - "asyncio.BaseEventLoop".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs index 0d19c4bb3c..0a46251d6d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs @@ -17,7 +17,7 @@ impl ExternalCodeType { impl CodeType for ExternalCodeType { fn type_label(&self) -> String { - self.name.clone() + super::PythonCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 8178fcc102..6a10a38e7f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -2,8 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -13,20 +14,43 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; use crate::backend::TemplateExpression; +use crate::bindings::python; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; mod primitives; mod record; +pub struct PythonBindingGenerator; + +impl BindingGenerator for PythonBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + python::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Python requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} + /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -114,6 +138,8 @@ pub struct Config { cdylib_name: Option<String>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, + #[serde(default)] + external_packages: HashMap<String, String>, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -133,6 +159,16 @@ impl Config { "uniffi".into() } } + + /// Get the package name for a given external namespace. + pub fn module_for_namespace(&self, ns: &str) -> String { + let ns = ns.to_string().to_snake_case(); + match self.external_packages.get(&ns) { + None => format!(".{ns}"), + Some(value) if value.is_empty() => ns, + Some(value) => format!("{value}.{ns}"), + } + } } impl BindingsConfig for Config { @@ -326,7 +362,19 @@ impl PythonCodeOracle { fixup_keyword(nm.to_string().to_shouty_snake_case()) } - fn ffi_type_label(ffi_type: &FfiType) -> String { + /// Get the idiomatic Python rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("UNIFFI_{}", nm.to_shouty_snake_case()) + } + + /// Get the idiomatic Python rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's use + // UpperCamelCase and reserve shouting for global variables + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "ctypes.c_int8".to_string(), FfiType::UInt8 => "ctypes.c_uint8".to_string(), @@ -338,19 +386,64 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), + FfiType::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), None => "_UniffiRustBuffer".to_string(), }, + FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(), FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(), - FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(), + FfiType::Callback(name) => self.ffi_callback_name(name), + FfiType::Struct(name) => self.ffi_struct_name(name), // Pointer to an `asyncio.EventLoop` instance - FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), - FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), - FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), - FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), - FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), + FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), + FfiType::VoidPointer => "ctypes.c_void_p".to_string(), + } + } + + /// Default values for FFI types + /// + /// Used to set a default return value when returning an error + fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 + | FfiType::Int8 + | FfiType::UInt16 + | FfiType::Int16 + | FfiType::UInt32 + | FfiType::Int32 + | FfiType::UInt64 + | FfiType::Int64 => "0".to_owned(), + FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(), + FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { + Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"), + None => "_UniffiRustBuffer.default()".to_owned(), + }, + _ => unimplemented!("FFI return type: {t:?}"), + }, + // When we need to use a value for void returns, we use a `u8` placeholder + None => "0".to_owned(), + } + } + + /// Get the name of the protocol and class name for an object. + /// + /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the protocol name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, obj: &Object) -> (String, String) { + let class_name = self.class_name(obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Protocol"), class_name) } } } @@ -392,7 +485,6 @@ impl<T: AsType> AsCodeType for T { Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -429,6 +521,10 @@ pub mod filters { Ok(format!("{}.lift", ffi_converter_name(as_ct)?)) } + pub(super) fn check_lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { + Ok(format!("{}.check_lower", ffi_converter_name(as_ct)?)) + } + pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> { Ok(format!("{}.lower", ffi_converter_name(as_ct)?)) } @@ -448,8 +544,18 @@ pub mod filters { Ok(as_ct.as_codetype().literal(literal)) } + // Get the idiomatic Python rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + Ok(Type::UInt64.as_codetype().literal(&literal)) + } + pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> { - Ok(PythonCodeOracle::ffi_type_label(type_)) + Ok(PythonCodeOracle.ffi_type_label(type_)) + } + + pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref())) } /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc). @@ -471,4 +577,51 @@ pub mod filters { pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> { Ok(PythonCodeOracle.enum_variant_name(nm)) } + + /// Get the idiomatic Python rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_callback_name(nm)) + } + + /// Get the idiomatic Python rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(PythonCodeOracle.ffi_struct_name(nm)) + } + + /// Get the idiomatic Python rendering of an individual enum variant. + pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { + Ok(PythonCodeOracle.object_names(obj)) + } + + /// Get the idiomatic Python rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let docstring = textwrap::dedent(docstring); + // Escape triple quotes to avoid syntax error + let escaped = docstring.replace(r#"""""#, r#"\"\"\""#); + + let wrapped = format!("\"\"\"\n{escaped}\n\"\"\""); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_docstring_escape() { + let docstring = r#""""This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: """ +And a even longer quote: """"""#; + + let expected = r#"""" +\"\"\"This is a docstring beginning with triple quotes. +Contains "quotes" in it. +It also has a triple quote: \"\"\" +And a even longer quote: \"\"\""" +""""#; + + assert_eq!(super::filters::docstring(docstring, &0).unwrap(), expected); + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py index 82aa534b46..26daa9ba5c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py @@ -3,13 +3,37 @@ _UNIFFI_RUST_FUTURE_POLL_READY = 0 _UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 # Stores futures for _uniffi_continuation_callback -_UniffiContinuationPointerManager = _UniffiPointerManager() +_UniffiContinuationHandleMap = _UniffiHandleMap() + +UNIFFI_GLOBAL_EVENT_LOOP = None + +""" +Set the event loop to use for async functions + +This is needed if some async functions run outside of the eventloop, for example: + - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the + Rust code spawning its own thread. + - The Rust code calls an async callback method from a sync callback function, using something + like `pollster` to block on the async call. + +In this case, we need an event loop to run the Python async function, but there's no eventloop set +for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case. +""" +def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop): + global UNIFFI_GLOBAL_EVENT_LOOP + UNIFFI_GLOBAL_EVENT_LOOP = eventloop + +def _uniffi_get_event_loop(): + if UNIFFI_GLOBAL_EVENT_LOOP is not None: + return UNIFFI_GLOBAL_EVENT_LOOP + else: + return asyncio.get_running_loop() # Continuation callback for async functions # lift the return value or error and resolve the future, causing the async function to resume. -@_UNIFFI_FUTURE_CONTINUATION_T +@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK def _uniffi_continuation_callback(future_ptr, poll_code): - (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr) + (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr) eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) def _uniffi_set_future_result(future, poll_code): @@ -18,14 +42,15 @@ def _uniffi_set_future_result(future, poll_code): async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): try: - eventloop = asyncio.get_running_loop() + eventloop = _uniffi_get_event_loop() # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value while True: future = eventloop.create_future() ffi_poll( rust_future, - _UniffiContinuationPointerManager.new_pointer((eventloop, future)), + _uniffi_continuation_callback, + _UniffiContinuationHandleMap.insert((eventloop, future)), ) poll_code = await future if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: @@ -37,4 +62,53 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, finally: ffi_free(rust_future) -_UniffiLib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(_uniffi_continuation_callback) +{%- if ci.has_async_callback_interface_definition() %} +def uniffi_trait_interface_call_async(make_call, handle_success, handle_error): + async def make_call_and_call_callback(): + try: + handle_success(await make_call()) + except Exception as e: + print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) + traceback.print_exc(file=sys.stderr) + handle_error( + _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(repr(e)), + ) + eventloop = _uniffi_get_event_loop() + task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) + handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) + return UniffiForeignFuture(handle, uniffi_foreign_future_free) + +def uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, error_type, lower_error): + async def make_call_and_call_callback(): + try: + try: + handle_success(await make_call()) + except error_type as e: + handle_error( + _UniffiRustCallStatus.CALL_ERROR, + lower_error(e), + ) + except Exception as e: + print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) + traceback.print_exc(file=sys.stderr) + handle_error( + _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, + {{ Type::String.borrow()|lower_fn }}(repr(e)), + ) + eventloop = _uniffi_get_event_loop() + task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) + handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) + return UniffiForeignFuture(handle, uniffi_foreign_future_free) + +UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap() + +@UNIFFI_FOREIGN_FUTURE_FREE +def uniffi_foreign_future_free(handle): + (eventloop, task) = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle) + eventloop.call_soon(uniffi_foreign_future_do_free, task) + +def uniffi_foreign_future_do_free(task): + if not task.done(): + task.cancel() +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py index 6775e9e132..3f8c5d1d4d 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py @@ -1,16 +1,20 @@ -class _UniffiConverterBool(_UniffiConverterPrimitive): +class _UniffiConverterBool: @classmethod - def check(cls, value): + def check_lower(cls, value): return not not value @classmethod + def lower(cls, value): + return 1 if value else 0 + + @staticmethod + def lift(value): + return value != 0 + + @classmethod def read(cls, buf): return cls.lift(buf.read_u8()) @classmethod - def write_unchecked(cls, value, buf): + def write(cls, value, buf): buf.write_u8(value) - - @staticmethod - def lift(value): - return value != 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py index 196b5b29fa..4d09531322 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py @@ -7,10 +7,13 @@ class _UniffiConverterBytes(_UniffiConverterRustBuffer): return buf.read(size) @staticmethod - def write(value, buf): + def check_lower(value): try: memoryview(value) except TypeError: raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) + + @staticmethod + def write(value, buf): buf.write_i32(len(value)) buf.write(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py new file mode 100644 index 0000000000..676f01177a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py @@ -0,0 +1,98 @@ +{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} +{%- let trait_impl=format!("UniffiTraitImpl{}", name) %} + +# Put all the bits inside a class to keep the top-level namespace clean +class {{ trait_impl }}: + # For each method, generate a callback function to pass to Rust + {%- for (ffi_callback, meth) in vtable_methods.iter() %} + + @{{ ffi_callback.name()|ffi_callback_name }} + def {{ meth.name()|fn_name }}( + {%- for arg in ffi_callback.arguments() %} + {{ arg.name()|var_name }}, + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() %} + uniffi_call_status_ptr, + {%- endif %} + ): + uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle) + def make_call(): + args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %}) + method = uniffi_obj.{{ meth.name()|fn_name }} + return method(*args) + + {% if !meth.is_async() %} + {%- match meth.return_type() %} + {%- when Some(return_type) %} + def write_return_value(v): + uniffi_out_return[0] = {{ return_type|lower_fn }}(v) + {%- when None %} + write_return_value = lambda v: None + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + _uniffi_trait_interface_call( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + ) + {%- when Some(error) %} + _uniffi_trait_interface_call_with_error( + uniffi_call_status_ptr.contents, + make_call, + write_return_value, + {{ error|type_name }}, + {{ error|lower_fn }}, + ) + {%- endmatch %} + {%- else %} + def handle_success(return_value): + uniffi_future_callback( + uniffi_callback_data, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ return_type|lower_fn }}(return_value), + {%- when None %} + {%- endmatch %} + _UniffiRustCallStatus.default() + ) + ) + + def handle_error(status_code, rust_buffer): + uniffi_future_callback( + uniffi_callback_data, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + {{ meth.return_type().map(FfiType::from)|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + _UniffiRustCallStatus(status_code, rust_buffer), + ) + ) + + {%- match meth.throws_type() %} + {%- when None %} + uniffi_out_return[0] = uniffi_trait_interface_call_async(make_call, handle_success, handle_error) + {%- when Some(error) %} + uniffi_out_return[0] = uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }}) + {%- endmatch %} + {%- endif %} + {%- endfor %} + + @{{ "CallbackInterfaceFree"|ffi_callback_name }} + def uniffi_free(uniffi_handle): + {{ ffi_converter_name }}._handle_map.remove(uniffi_handle) + + # Generate the FFI VTable. This has a field for each callback interface method. + uniffi_vtable = {{ vtable|ffi_type_name }}( + {%- for (_, meth) in vtable_methods.iter() %} + {{ meth.name()|fn_name }}, + {%- endfor %} + uniffi_free + ) + # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever, + # or else bad things will happen when Rust tries to access it. + _UniffiLib.{{ ffi_init_callback.name() }}(ctypes.byref(uniffi_vtable)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py index 0fe2ab8dc0..d802c90fde 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py @@ -1,42 +1,3 @@ -import threading - -class ConcurrentHandleMap: - """ - A map where inserting, getting and removing data is synchronized with a lock. - """ - - def __init__(self): - # type Handle = int - self._left_map = {} # type: Dict[Handle, Any] - self._right_map = {} # type: Dict[Any, Handle] - - self._lock = threading.Lock() - self._current_handle = 0 - self._stride = 1 - - - def insert(self, obj): - with self._lock: - if obj in self._right_map: - return self._right_map[obj] - else: - handle = self._current_handle - self._current_handle += self._stride - self._left_map[handle] = obj - self._right_map[obj] = handle - return handle - - def get(self, handle): - with self._lock: - return self._left_map.get(handle) - - def remove(self, handle): - with self._lock: - if handle in self._left_map: - obj = self._left_map.pop(handle) - del self._right_map[obj] - return obj - # Magic number for the Rust proxy to call using the same mechanism as every other method, # to free the callback once it's dropped by Rust. IDX_CALLBACK_FREE = 0 @@ -45,22 +6,12 @@ _UNIFFI_CALLBACK_SUCCESS = 0 _UNIFFI_CALLBACK_ERROR = 1 _UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -class _UniffiConverterCallbackInterface: - _handle_map = ConcurrentHandleMap() - - def __init__(self, cb): - self._foreign_callback = cb - - def drop(self, handle): - self.__class__._handle_map.remove(handle) +class UniffiCallbackInterfaceFfiConverter: + _handle_map = _UniffiHandleMap() @classmethod def lift(cls, handle): - obj = cls._handle_map.get(handle) - if not obj: - raise InternalError("The object in the handle map has been dropped already") - - return obj + return cls._handle_map.get(handle) @classmethod def read(cls, buf): @@ -68,6 +19,10 @@ class _UniffiConverterCallbackInterface: cls.lift(handle) @classmethod + def check_lower(cls, cb): + pass + + @classmethod def lower(cls, cb): handle = cls._handle_map.insert(cb) return handle diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py index e0e926757a..a41e58e635 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py @@ -1,105 +1,13 @@ -{%- let cbi = ci|get_callback_interface_definition(id) %} -{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} +{%- let cbi = ci|get_callback_interface_definition(name) %} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} +{%- let protocol_name = type_name.clone() %} +{%- let protocol_docstring = cbi.docstring() %} +{%- let vtable = cbi.vtable() %} +{%- let methods = cbi.methods() %} +{%- let vtable_methods = cbi.vtable_methods() %} -{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %} - -# Declaration and _UniffiConverters for {{ type_name }} Callback Interface - -class {{ type_name }}: - {% for meth in cbi.methods() -%} - def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): - raise NotImplementedError - - {% endfor %} - -def py_{{ foreign_callback }}(handle, method, args_data, args_len, buf_ptr): - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name %} - def {{ method_name }}(python_callback, args_stream, buf_ptr): - {#- Unpacking args from the _UniffiRustBuffer #} - def makeCall(): - {#- Calling the concrete callback object #} - {%- if meth.arguments().len() != 0 -%} - return python_callback.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {{ arg|read_fn }}(args_stream) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - {%- else %} - return python_callback.{{ meth.name()|fn_name }}() - {%- endif %} - - def makeCallAndHandleReturn(): - {%- match meth.return_type() %} - {%- when Some(return_type) %} - rval = makeCall() - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ return_type|write_fn }}(rval, builder) - buf_ptr[0] = builder.finalize() - {%- when None %} - makeCall() - {%- endmatch %} - return _UNIFFI_CALLBACK_SUCCESS - - {%- match meth.throws_type() %} - {%- when None %} - return makeCallAndHandleReturn() - {%- when Some(err) %} - try: - return makeCallAndHandleReturn() - except {{ err|type_name }} as e: - # Catch errors declared in the UDL file - with _UniffiRustBuffer.alloc_with_builder() as builder: - {{ err|write_fn }}(e, builder) - buf_ptr[0] = builder.finalize() - return _UNIFFI_CALLBACK_ERROR - {%- endmatch %} - - {% endfor %} - - cb = {{ ffi_converter_name }}.lift(handle) - if not cb: - raise InternalError("No callback in handlemap; this is a uniffi bug") - - if method == IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.drop(handle) - # Successfull return - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_SUCCESS - - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - if method == {{ loop.index }}: - # Call the method and handle any errors - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details - try: - return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr) - except BaseException as e: - # Catch unexpected errors - try: - # Try to serialize the exception into a String - buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e)) - except: - # If that fails, just give up - pass - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - {% endfor %} - - # This should never happen, because an out of bounds method index won't - # ever be used. Once we can catch errors, we should return an InternalException. - # https://github.com/mozilla/uniffi-rs/issues/351 - - # An unexpected error happened. - # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return _UNIFFI_CALLBACK_UNEXPECTED_ERROR - -# We need to keep this function reference alive: -# if they get GC'd while in use then UniFFI internals could attempt to call a function -# that is in freed memory. -# That would be...uh...bad. Yeah, that's the word. Bad. -{{ foreign_callback }} = _UNIFFI_FOREIGN_CALLBACK_T(py_{{ foreign_callback }}) -_rust_call(lambda err: _UniffiLib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)) +{% include "Protocol.py" %} +{% include "CallbackInterfaceImpl.py" %} # The _UniffiConverter which transforms the Callbacks in to Handles to pass to Rust. -{{ ffi_converter_name }} = _UniffiConverterCallbackInterface({{ foreign_callback }}) +{{ ffi_converter_name }} = UniffiCallbackInterfaceFfiConverter() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py index 5be6155b84..f75a85dc27 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py @@ -18,6 +18,10 @@ class _UniffiConverterType{{ name }}: return {{ builtin|ffi_converter_name }}.lift(value) @staticmethod + def check_lower(value): + return {{ builtin|ffi_converter_name }}.check_lower(value) + + @staticmethod def lower(value): return {{ builtin|ffi_converter_name }}.lower(value) @@ -52,6 +56,11 @@ class _UniffiConverterType{{ name }}: return {{ config.into_custom.render("builtin_value") }} @staticmethod + def check_lower(value): + builtin_value = {{ config.from_custom.render("value") }} + return {{ builtin|check_lower_fn }}(builtin_value) + + @staticmethod def lower(value): builtin_value = {{ config.from_custom.render("value") }} return {{ builtin|lower_fn }}(builtin_value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py index 10974e009d..ecb035b7f4 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py @@ -12,10 +12,14 @@ class _UniffiConverterDuration(_UniffiConverterRustBuffer): return datetime.timedelta(seconds=seconds, microseconds=microseconds) @staticmethod - def write(value, buf): + def check_lower(value): seconds = value.seconds + value.days * 24 * 3600 - nanoseconds = value.microseconds * 1000 if seconds < 0: raise ValueError("Invalid duration, must be non-negative") + + @staticmethod + def write(value, buf): + seconds = value.seconds + value.days * 24 * 3600 + nanoseconds = value.microseconds * 1000 buf.write_i64(seconds) buf.write_u32(nanoseconds) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py index 84d089baf9..d07dd1c44a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py @@ -7,31 +7,59 @@ {% if e.is_flat() %} class {{ type_name }}(enum.Enum): - {% for variant in e.variants() -%} - {{ variant.name()|enum_variant_py }} = {{ loop.index }} + {%- call py::docstring(e, 4) %} + {%- for variant in e.variants() %} + {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }} + {%- call py::docstring(variant, 4) %} {% endfor %} {% else %} class {{ type_name }}: + {%- call py::docstring(e, 4) %} def __init__(self): raise RuntimeError("{{ type_name }} cannot be instantiated directly") # Each enum variant is a nested class of the enum itself. {% for variant in e.variants() -%} class {{ variant.name()|enum_variant_py }}: - {% for field in variant.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(variant, 8) %} + + {%- if variant.has_nameless_fields() %} + def __init__(self, *values): + if len(values) != {{ variant.fields().len() }}: + raise TypeError(f"Expected a tuple of len {{ variant.fields().len() }}, found len {len(values)}") + {%- for field in variant.fields() %} + if not isinstance(values[{{ loop.index0 }}], {{ field|type_name }}): + raise TypeError(f"unexpected type for tuple element {{ loop.index0 }} - expected '{{ field|type_name }}', got '{type(values[{{ loop.index0 }}])}'") + {%- endfor %} + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}" + + def __eq__(self, other): + if not other.is_{{ variant.name()|var_name }}(): + return False + return self._values == other._values + + {%- else -%} + {%- for field in variant.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 8) %} {%- endfor %} @typing.no_type_check def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}): - {% if variant.has_fields() %} + {%- if variant.has_fields() %} {%- for field in variant.fields() %} self.{{ field.name()|var_name }} = {{ field.name()|var_name }} {%- endfor %} - {% else %} + {%- else %} pass - {% endif %} + {%- endif %} def __str__(self): return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) @@ -44,6 +72,7 @@ class {{ type_name }}: return False {%- endfor %} return True + {% endif %} {% endfor %} # For each variant, we have an `is_NAME` method for easily checking @@ -81,6 +110,30 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): {%- endfor %} raise InternalError("Raw enum value doesn't match any cases") + @staticmethod + def check_lower(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + {%- if e.is_flat() %} + if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}: + {%- else %} + if value.is_{{ variant.name()|var_name }}(): + {%- endif %} + {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() %} + {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}]) + {%- else %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endif %} + {%- endfor %} + return + {%- endfor %} + raise ValueError(value) + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} {%- if e.is_flat() %} @@ -90,7 +143,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): if value.is_{{ variant.name()|var_name }}(): buf.write_i32({{ loop.index }}) {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() %} + {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf) + {%- else %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) + {%- endif %} {%- endfor %} {%- endif %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py index 26a1e6452a..0911ff559a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py @@ -5,6 +5,7 @@ # __dict__. All of this happens in dummy class to avoid polluting the module # namespace. class {{ type_name }}(Exception): + {%- call py::docstring(e, 4) %} pass _UniffiTemp{{ type_name }} = {{ type_name }} @@ -14,10 +15,14 @@ class {{ type_name }}: # type: ignore {%- let variant_type_name = variant.name()|class_name -%} {%- if e.is_flat() %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __repr__(self): return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self))) {%- else %} class {{ variant_type_name }}(_UniffiTemp{{ type_name }}): + {%- call py::docstring(variant, 8) %} + def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}): {%- if variant.has_fields() %} super().__init__(", ".join([ @@ -60,6 +65,20 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): raise InternalError("Raw enum value doesn't match any cases") @staticmethod + def check_lower(value): + {%- if e.variants().is_empty() %} + pass + {%- else %} + {%- for variant in e.variants() %} + if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): + {%- for field in variant.fields() %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + return + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): {%- for variant in e.variants() %} if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}): diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py index 71e05e8b06..6c0cee85ef 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py @@ -1,9 +1,9 @@ -{%- let ns = namespace|fn_name %} +{%- let module = python_config.module_for_namespace(namespace) -%} # External type {{name}} is in namespace "{{namespace}}", crate {{module_path}} {%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %} -{{ self.add_import_of(ns, ffi_converter_name) }} -{{ self.add_import_of(ns, name) }} {#- import the type alias itself -#} +{{ self.add_import_of(module, ffi_converter_name) }} +{{ self.add_import_of(module, name|class_name) }} {#- import the type alias itself -#} {%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %} -{{ self.add_import_of_as(ns, "_UniffiRustBuffer", rustbuffer_local_name) }} +{{ self.add_import_of_as(module, "_UniffiRustBuffer", rustbuffer_local_name) }} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py index a52107a638..49a1a7286e 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py @@ -4,5 +4,5 @@ class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): return buf.read_float() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_float(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py index 772f5080e9..e2084c7b13 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py @@ -4,5 +4,5 @@ class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): return buf.read_double() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_double(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py deleted file mode 100644 index 6a6932fed0..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py +++ /dev/null @@ -1,63 +0,0 @@ -# FFI code for the ForeignExecutor type - -{{ self.add_import("asyncio") }} - -_UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0 -_UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED = 1 -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2 - -class {{ ffi_converter_name }}: - _pointer_manager = _UniffiPointerManager() - - @classmethod - def lower(cls, eventloop): - if not isinstance(eventloop, asyncio.BaseEventLoop): - raise TypeError("_uniffi_executor_callback: Expected EventLoop instance") - return cls._pointer_manager.new_pointer(eventloop) - - @classmethod - def write(cls, eventloop, buf): - buf.write_c_size_t(cls.lower(eventloop)) - - @classmethod - def read(cls, buf): - return cls.lift(buf.read_c_size_t()) - - @classmethod - def lift(cls, value): - return cls._pointer_manager.lookup(value) - -@_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T -def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data): - if task_ptr is None: - {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address) - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - else: - eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address) - if eventloop.is_closed(): - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED - - callback = _UNIFFI_RUST_TASK(task_ptr) - # FIXME: there's no easy way to get a callback when an eventloop is closed. This means that - # if eventloop is called before the `call_soon_threadsafe()` calls are invoked, the call - # will never happen and we will probably leak a resource. - if delay == 0: - # This can be called from any thread, so make sure to use `call_soon_threadsafe' - eventloop.call_soon_threadsafe(callback, task_data, - _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) - else: - # For delayed tasks, we use `call_soon_threadsafe()` + `call_later()` to make the - # operation threadsafe - eventloop.call_soon_threadsafe(eventloop.call_later, delay / 1000.0, callback, - task_data, _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS) - return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - -# Register the callback with the scaffolding -{%- match ci.ffi_foreign_executor_callback_set() %} -{%- when Some with (fn) %} -_UniffiLib.{{ fn.name() }}(_uniffi_executor_callback) -{%- when None %} -{#- No foreign executor, we don't set anything #} -{% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py new file mode 100644 index 0000000000..f7c13cf745 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py @@ -0,0 +1,33 @@ +class _UniffiHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._map = {} # type: Dict[Handle, Any] + self._lock = threading.Lock() + self._counter = itertools.count() + + def insert(self, obj): + with self._lock: + handle = next(self._counter) + self._map[handle] = obj + return handle + + def get(self, handle): + try: + with self._lock: + return self._map[handle] + except KeyError: + raise InternalError("UniffiHandleMap.get: Invalid handle") + + def remove(self, handle): + try: + with self._lock: + return self._map.pop(handle) + except KeyError: + raise InternalError("UniffiHandleMap.remove: Invalid handle") + + def __len__(self): + return len(self._map) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py index dca962f176..5d4bcbba89 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py @@ -16,15 +16,19 @@ class _UniffiRustCallStatus(ctypes.Structure): # These match the values from the uniffi::rustcalls module CALL_SUCCESS = 0 CALL_ERROR = 1 - CALL_PANIC = 2 + CALL_UNEXPECTED_ERROR = 2 + + @staticmethod + def default(): + return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default()) def __str__(self): if self.code == _UniffiRustCallStatus.CALL_SUCCESS: return "_UniffiRustCallStatus(CALL_SUCCESS)" elif self.code == _UniffiRustCallStatus.CALL_ERROR: return "_UniffiRustCallStatus(CALL_ERROR)" - elif self.code == _UniffiRustCallStatus.CALL_PANIC: - return "_UniffiRustCallStatus(CALL_PANIC)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" else: return "_UniffiRustCallStatus(<invalid code>)" @@ -37,7 +41,7 @@ def _rust_call_with_error(error_ffi_converter, fn, *args): # # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. - call_status = _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer(0, 0, None)) + call_status = _UniffiRustCallStatus.default() args_with_error = args + (ctypes.byref(call_status),) result = fn(*args_with_error) @@ -53,7 +57,7 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") else: raise error_ffi_converter.lift(call_status.error_buf) - elif call_status.code == _UniffiRustCallStatus.CALL_PANIC: + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer # with the message. But if that code panics, then it just sends back # an empty buffer. @@ -66,10 +70,20 @@ def _uniffi_check_call_status(error_ffi_converter, call_status): raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( call_status.code)) -# A function pointer for a callback as defined by UniFFI. -# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int` -_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer)) - -# UniFFI future continuation -_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8) +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py index 99f19dc1c0..befa563384 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt16(_UniffiConverterPrimitiveInt): return buf.read_i16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py index 3b142c8749..70644f6717 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt32(_UniffiConverterPrimitiveInt): return buf.read_i32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py index 6e94379cbf..232f127bd6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt64(_UniffiConverterPrimitiveInt): return buf.read_i64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py index 732530e3cb..c1de1625e7 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterInt8(_UniffiConverterPrimitiveInt): return buf.read_i8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_i8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py index 387227ed09..a09ca28a30 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py @@ -3,6 +3,12 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, items): + for (key, value) in items.items(): + {{ key_ffi_converter }}.check_lower(key) + {{ value_ffi_converter }}.check_lower(value) + + @classmethod def write(cls, items, buf): buf.write_i32(len(items)) for (key, value) in items.items(): diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py index fac6cd5564..1929f9aad6 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py @@ -1,22 +1,6 @@ # Define some ctypes FFI types that we use in the library """ -ctypes type for the foreign executor callback. This is a built-in interface for scheduling -tasks - -Args: - executor: opaque c_size_t value representing the eventloop - delay: delay in ms - task: function pointer to the task callback - task_data: void pointer to the task callback data - -Normally we should call task(task_data) after the detail. -However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should -decrease the EventLoop refcount. -""" -_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p) - -""" Function pointer for a Rust task, which a callback function that takes a opaque pointer """ _UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) @@ -25,7 +9,7 @@ def _uniffi_future_callback_t(return_type): """ Factory function to create callback function types for async functions """ - return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus) + return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus) def _uniffi_load_indirect(): """ @@ -72,12 +56,37 @@ def _uniffi_check_api_checksums(lib): # This is an implementation detail which will be called internally by the public API. _UniffiLib = _uniffi_load_indirect() -{%- for func in ci.iter_ffi_function_definitions() %} + +{%- for def in ci.ffi_definitions() %} +{%- match def %} +{%- when FfiDefinition::CallbackFunction(callback) %} +{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE( + {%- match callback.return_type() %} + {%- when Some(return_type) %}{{ return_type|ffi_type_name }}, + {%- when None %}None, + {%- endmatch %} + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|ffi_type_name }}, + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + ctypes.POINTER(_UniffiRustCallStatus), + {%- endif %} +) +{%- when FfiDefinition::Struct(ffi_struct) %} +class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure): + _fields_ = [ + {%- for field in ffi_struct.fields() %} + ("{{ field.name()|var_name }}", {{ field.type_().borrow()|ffi_type_name }}), + {%- endfor %} + ] +{%- when FfiDefinition::Function(func) %} _UniffiLib.{{ func.name() }}.argtypes = ( {%- call py::arg_list_ffi_decl(func) -%} ) _UniffiLib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %} +{%- endmatch %} {%- endfor %} + {# Ensure to call the contract verification only after we defined all functions. -#} _uniffi_check_contract_api_version(_UniffiLib) _uniffi_check_api_checksums(_UniffiLib) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 7e98f7c46f..18dca4743a 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -1,14 +1,33 @@ {%- let obj = ci|get_object_definition(name) %} - -class {{ type_name }}: +{%- let (protocol_name, impl_name) = obj|object_names %} +{%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} + +{% include "Protocol.py" %} + +{% if ci.is_name_used_as_error(name) %} +class {{ impl_name }}(Exception): +{%- else %} +class {{ impl_name }}: +{%- endif %} + {%- call py::docstring(obj, 4) %} _pointer: ctypes.c_void_p {%- match obj.primary_constructor() %} {%- when Some with (cons) %} +{%- if cons.is_async() %} + def __init__(self, *args, **kw): + raise ValueError("async constructors not supported.") +{%- else %} def __init__(self, {% call py::arg_list_decl(cons) -%}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} self._pointer = {% call py::to_ffi_call(cons) %} +{%- endif %} {%- when None %} + {# no __init__ means simple construction without a pointer works, which can confuse #} + def __init__(self, *args, **kwargs): + raise ValueError("This class has no default constructor") {%- endmatch %} def __del__(self): @@ -17,6 +36,9 @@ class {{ type_name }}: if pointer is not None: _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer) + def _uniffi_clone_pointer(self): + return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._pointer) + # Used by alternative constructors or any methods which return this type. @classmethod def _make_instance_(cls, pointer): @@ -29,17 +51,32 @@ class {{ type_name }}: {%- for cons in obj.alternate_constructors() %} @classmethod +{%- if cons.is_async() %} + async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} + {%- call py::setup_args_extra_indent(cons) %} + + return await _uniffi_rust_call_async( + _UniffiLib.{{ cons.ffi_func().name() }}({% call py::arg_list_lowered(cons) %}), + _UniffiLib.{{ cons.ffi_rust_future_poll(ci) }}, + _UniffiLib.{{ cons.ffi_rust_future_complete(ci) }}, + _UniffiLib.{{ cons.ffi_rust_future_free(ci) }}, + {{ ffi_converter_name }}.lift, + {% call py::error_ffi_converter(cons) %} + ) +{%- else %} def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): + {%- call py::docstring(cons, 8) %} {%- call py::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. pointer = {% call py::to_ffi_call(cons) %} return cls._make_instance_(pointer) +{%- endif %} {% endfor %} {%- for meth in obj.methods() -%} {%- call py::method_decl(meth.name()|fn_name, meth) %} -{% endfor %} - +{%- endfor %} {%- for tm in obj.uniffi_traits() -%} {%- match tm %} {%- when UniffiTrait::Debug { fmt } %} @@ -51,37 +88,84 @@ class {{ type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %}) + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", eq) %}) def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %}) + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", ne) %}) {%- when UniffiTrait::Hash { hash } %} {%- call py::method_decl("__hash__", hash) %} -{% endmatch %} -{% endfor %} - - -class {{ ffi_converter_name }}: +{%- endmatch %} +{%- endfor %} + +{%- if obj.has_callback_interface() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{% include "CallbackInterfaceImpl.py" %} +{%- endif %} + +{# Objects as error #} +{%- if ci.is_name_used_as_error(name) %} +{# Due to some mismatches in the ffi converter mechanisms, errors are forced to be a RustBuffer #} +class {{ ffi_converter_name }}__as_error(_UniffiConverterRustBuffer): @classmethod def read(cls, buf): - ptr = buf.read_u64() - if ptr == 0: - raise InternalError("Raw pointer value was null") - return cls.lift(ptr) + raise NotImplementedError() @classmethod def write(cls, value, buf): - if not isinstance(value, {{ type_name }}): - raise TypeError("Expected {{ type_name }} instance, {} found".format(type(value).__name__)) - buf.write_u64(cls.lower(value)) + raise NotImplementedError() @staticmethod def lift(value): - return {{ type_name }}._make_instance_(value) + # Errors are always a rust buffer holding a pointer - which is a "read" + with value.consume_with_stream() as stream: + return {{ ffi_converter_name }}.read(stream) @staticmethod def lower(value): - return value._pointer + raise NotImplementedError() + +{%- endif %} + +class {{ ffi_converter_name }}: + {%- if obj.has_callback_interface() %} + _handle_map = _UniffiHandleMap() + {%- endif %} + + @staticmethod + def lift(value: int): + return {{ impl_name }}._make_instance_(value) + + @staticmethod + def check_lower(value: {{ type_name }}): + {%- if obj.has_callback_interface() %} + pass + {%- else %} + if not isinstance(value, {{ impl_name }}): + raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) + {%- endif %} + + @staticmethod + def lower(value: {{ protocol_name }}): + {%- if obj.has_callback_interface() %} + return {{ ffi_converter_name }}._handle_map.insert(value) + {%- else %} + if not isinstance(value, {{ impl_name }}): + raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) + return value._uniffi_clone_pointer() + {%- endif %} + + @classmethod + def read(cls, buf: _UniffiRustBuffer): + ptr = buf.read_u64() + if ptr == 0: + raise InternalError("Raw pointer value was null") + return cls.lift(ptr) + + @classmethod + def write(cls, value: {{ protocol_name }}, buf: _UniffiRustBuffer): + buf.write_u64(cls.lower(value)) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py index e406c51d49..4c07ae3e34 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py @@ -2,6 +2,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, value): + if value is not None: + {{ inner_ffi_converter }}.check_lower(value) + + @classmethod def write(cls, value, buf): if value is None: buf.write_u8(0) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py deleted file mode 100644 index 23aa28eab4..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py +++ /dev/null @@ -1,68 +0,0 @@ -class _UniffiPointerManagerCPython: - """ - Manage giving out pointers to Python objects on CPython - - This class is used to generate opaque pointers that reference Python objects to pass to Rust. - It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative. - """ - - def new_pointer(self, obj): - """ - Get a pointer for an object as a ctypes.c_size_t instance - - Each call to new_pointer() must be balanced with exactly one call to release_pointer() - - This returns a ctypes.c_size_t. This is always the same size as a pointer and can be - interchanged with pointers for FFI function arguments and return values. - """ - # IncRef the object since we're going to pass a pointer to Rust - ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj)) - # id() is the object address on CPython - # (https://docs.python.org/3/library/functions.html#id) - return id(obj) - - def release_pointer(self, address): - py_obj = ctypes.cast(address, ctypes.py_object) - obj = py_obj.value - ctypes.pythonapi.Py_DecRef(py_obj) - return obj - - def lookup(self, address): - return ctypes.cast(address, ctypes.py_object).value - -class _UniffiPointerManagerGeneral: - """ - Manage giving out pointers to Python objects on non-CPython platforms - - This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on - CPython and is slightly slower. - - Instead of using real pointers, it maps integer values to objects and returns the keys as - c_size_t values. - """ - - def __init__(self): - self._map = {} - self._lock = threading.Lock() - self._current_handle = 0 - - def new_pointer(self, obj): - with self._lock: - handle = self._current_handle - self._current_handle += 1 - self._map[handle] = obj - return handle - - def release_pointer(self, handle): - with self._lock: - return self._map.pop(handle) - - def lookup(self, handle): - with self._lock: - return self._map[handle] - -# Pick an pointer manager implementation based on the platform -if platform.python_implementation() == 'CPython': - _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore -else: - _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py new file mode 100644 index 0000000000..3b7e93596a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py @@ -0,0 +1,9 @@ +class {{ protocol_name }}(typing.Protocol): + {%- call py::docstring_value(protocol_docstring, 4) %} + {%- for meth in methods.iter() %} + def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}): + {%- call py::docstring(meth, 8) %} + raise NotImplementedError + {%- else %} + pass + {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py index 99a30e120f..0b5634eb52 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py @@ -1,11 +1,14 @@ {%- let rec = ci|get_record_definition(name) %} class {{ type_name }}: - {% for field in rec.fields() %} - {{- field.name()|var_name }}: "{{- field|type_name }}"; + {%- call py::docstring(rec, 4) %} + {%- for field in rec.fields() %} + {{ field.name()|var_name }}: "{{ field|type_name }}" + {%- call py::docstring(field, 4) %} {%- endfor %} + {%- if rec.has_fields() %} @typing.no_type_check - def __init__(self, {% for field in rec.fields() %} + def __init__(self, *, {% for field in rec.fields() %} {{- field.name()|var_name }}: "{{- field|type_name }}" {%- if field.default_value().is_some() %} = _DEFAULT{% endif %} {%- if !loop.last %}, {% endif %} @@ -22,6 +25,7 @@ class {{ type_name }}: self.{{ field_name }} = {{ field_name }} {%- endmatch %} {%- endfor %} + {%- endif %} def __str__(self): return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) @@ -43,7 +47,21 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer): ) @staticmethod + def check_lower(value): + {%- if rec.fields().is_empty() %} + pass + {%- else %} + {%- for field in rec.fields() %} + {{ field|check_lower_fn }}(value.{{ field.name()|var_name }}) + {%- endfor %} + {%- endif %} + + @staticmethod def write(value, buf): + {%- if rec.has_fields() %} {%- for field in rec.fields() %} {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf) {%- endfor %} + {%- else %} + pass + {%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py index daabd5b4b9..4db74fb157 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py @@ -1,28 +1,16 @@ # Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. class _UniffiConverterPrimitive: @classmethod - def check(cls, value): - return value - - @classmethod def lift(cls, value): return value @classmethod def lower(cls, value): - return cls.lowerUnchecked(cls.check(value)) - - @classmethod - def lowerUnchecked(cls, value): return value - @classmethod - def write(cls, value, buf): - cls.write_unchecked(cls.check(value), buf) - class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): @classmethod - def check(cls, value): + def check_lower(cls, value): try: value = value.__index__() except Exception: @@ -31,18 +19,16 @@ class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) if not cls.VALUE_MIN <= value < cls.VALUE_MAX: raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) - return super().check(value) class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): @classmethod - def check(cls, value): + def check_lower(cls, value): try: value = value.__float__() except Exception: raise TypeError("must be real number, not {}".format(type(value).__name__)) if not isinstance(value, float): raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) - return super().check(value) # Helper class for wrapper types that will always go through a _UniffiRustBuffer. # Classes should inherit from this and implement the `read` and `write` static methods. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py index c317a632fc..44e0ba1001 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py @@ -1,12 +1,16 @@ class _UniffiRustBuffer(ctypes.Structure): _fields_ = [ - ("capacity", ctypes.c_int32), - ("len", ctypes.c_int32), + ("capacity", ctypes.c_uint64), + ("len", ctypes.c_uint64), ("data", ctypes.POINTER(ctypes.c_char)), ] @staticmethod + def default(): + return _UniffiRustBuffer(0, 0, None) + + @staticmethod def alloc(size): return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}, size) @@ -137,9 +141,6 @@ class _UniffiRustBufferStream: def read_double(self): return self._unpack_from(8, ">d") - def read_c_size_t(self): - return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N") - class _UniffiRustBufferBuilder: """ Helper for structured writing of bytes into a _UniffiRustBuffer. diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py index 3c9f5a4596..3c30d9f9f5 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py @@ -2,6 +2,11 @@ class {{ ffi_converter_name}}(_UniffiConverterRustBuffer): @classmethod + def check_lower(cls, value): + for item in value: + {{ inner_ffi_converter }}.check_lower(item) + + @classmethod def write(cls, value, buf): items = len(value) buf.write_i32(items) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py index 40890b6abc..d574edd09f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py @@ -1,6 +1,6 @@ class _UniffiConverterString: @staticmethod - def check(value): + def check_lower(value): if not isinstance(value, str): raise TypeError("argument must be str, not {}".format(type(value).__name__)) return value @@ -15,7 +15,6 @@ class _UniffiConverterString: @staticmethod def write(value, buf): - value = _UniffiConverterString.check(value) utf8_bytes = value.encode("utf-8") buf.write_i32(len(utf8_bytes)) buf.write(utf8_bytes) @@ -27,7 +26,6 @@ class _UniffiConverterString: @staticmethod def lower(value): - value = _UniffiConverterString.check(value) with _UniffiRustBuffer.alloc_with_builder() as builder: builder.write(value.encode("utf-8")) return builder.finalize() diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py index 8402f6095d..76d7f8bcdb 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py @@ -18,6 +18,10 @@ class _UniffiConverterTimestamp(_UniffiConverterRustBuffer): return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds) @staticmethod + def check_lower(value): + pass + + @staticmethod def write(value, buf): if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc): sign = 1 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py index f258b60a1c..230b9e853f 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py @@ -1,7 +1,15 @@ {%- if func.is_async() %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): - return _uniffi_rust_call_async( +{%- match func.return_type() -%} +{%- when Some with (return_type) %} +async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": +{% when None %} +async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: +{% endmatch %} + + {%- call py::docstring(func, 4) %} + {%- call py::setup_args(func) %} + return await _uniffi_rust_call_async( _UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}), _UniffiLib.{{func.ffi_rust_future_poll(ci) }}, _UniffiLib.{{func.ffi_rust_future_complete(ci) }}, @@ -13,13 +21,7 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when None %} lambda val: None, {% endmatch %} - # Error FFI converter - {%- match func.throws_type() %} - {%- when Some(e) %} - {{ e|ffi_converter_name }}, - {%- when None %} - None, - {%- endmatch %} + {% call py::error_ffi_converter(func) %} ) {%- else %} @@ -27,11 +29,13 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): {%- when Some with (return_type) %} def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}": + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %}) {% when None %} -def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}): +def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None: + {%- call py::docstring(func, 4) %} {%- call py::setup_args(func) %} {% call py::to_ffi_call(func) %} {% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py index 5e05314c37..4aaed253e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py @@ -85,7 +85,7 @@ {%- when Type::Map { key_type, value_type } %} {%- include "MapTemplate.py" %} -{%- when Type::CallbackInterface { name: id, module_path } %} +{%- when Type::CallbackInterface { name, module_path } %} {%- include "CallbackInterfaceTemplate.py" %} {%- when Type::Custom { name, module_path, builtin } %} @@ -94,9 +94,6 @@ {%- when Type::External { name, module_path, namespace, kind, tagged } %} {%- include "ExternalTemplate.py" %} -{%- when Type::ForeignExecutor %} -{%- include "ForeignExecutorTemplate.py" %} - {%- else %} {%- endmatch %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py index 081c6731ce..039bf76162 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt): return buf.read_u16() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u16(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py index b80e75177d..1650bf9b60 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt): return buf.read_u32() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u32(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py index 4b87e58547..f354545e26 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt64(_UniffiConverterPrimitiveInt): return buf.read_u64() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u64(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py index 33026706f2..cee130b4d9 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py @@ -8,5 +8,5 @@ class _UniffiConverterUInt8(_UniffiConverterPrimitiveInt): return buf.read_u8() @staticmethod - def write_unchecked(value, buf): + def write(value, buf): buf.write_u8(value) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py index ef3b1bb94d..6818a8c107 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -5,27 +5,29 @@ #} {%- macro to_ffi_call(func) -%} - {%- match func.throws_type() -%} - {%- when Some with (e) -%} -_rust_call_with_error({{ e|ffi_converter_name }}, - {%- else -%} -_rust_call( - {%- endmatch -%} - _UniffiLib.{{ func.ffi_func().name() }}, - {%- call arg_list_lowered(func) -%} -) +{%- call _to_ffi_call_with_prefix_arg("", func) %} {%- endmacro -%} {%- macro to_ffi_call_with_prefix(prefix, func) -%} - {%- match func.throws_type() -%} - {%- when Some with (e) -%} -_rust_call_with_error( - {{ e|ffi_converter_name }}, - {%- else -%} +{%- call _to_ffi_call_with_prefix_arg(format!("{},", prefix), func) %} +{%- endmacro -%} + +{%- macro _to_ffi_call_with_prefix_arg(prefix, func) -%} +{%- match func.throws_type() -%} +{%- when Some with (e) -%} +{%- match e -%} +{%- when Type::Enum { name, module_path } -%} +_rust_call_with_error({{ e|ffi_converter_name }}, +{%- when Type::Object { name, module_path, imp } -%} +_rust_call_with_error({{ e|ffi_converter_name }}__as_error, +{%- else %} +# unsupported error type! +{%- endmatch %} +{%- else -%} _rust_call( - {%- endmatch -%} +{%- endmatch -%} _UniffiLib.{{ func.ffi_func().name() }}, - {{- prefix }}, + {{- prefix }} {%- call arg_list_lowered(func) -%} ) {%- endmacro -%} @@ -37,6 +39,19 @@ _rust_call( {%- endfor %} {%- endmacro -%} +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{{ "" }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} + {#- // Arglist as used in Python declarations of methods, functions and constructors. // Note the var_name and type_name filters. @@ -76,6 +91,7 @@ _rust_call( if {{ arg.name()|var_name }} is _DEFAULT: {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} + {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) {% endfor -%} {%- endmacro -%} @@ -91,6 +107,7 @@ _rust_call( if {{ arg.name()|var_name }} is _DEFAULT: {{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }} {%- endmatch %} + {{ arg|check_lower_fn }}({{ arg.name()|var_name }}) {% endfor -%} {%- endmacro -%} @@ -100,11 +117,18 @@ _rust_call( {%- macro method_decl(py_method_name, meth) %} {% if meth.is_async() %} - def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): +{%- match meth.return_type() %} +{%- when Some with (return_type) %} + async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": +{%- when None %} + async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None: +{% endmatch %} + + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - return _uniffi_rust_call_async( + return await _uniffi_rust_call_async( _UniffiLib.{{ meth.ffi_func().name() }}( - self._pointer, {% call arg_list_lowered(meth) %} + self._uniffi_clone_pointer(), {% call arg_list_lowered(meth) %} ), _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, @@ -116,13 +140,7 @@ _rust_call( {%- when None %} lambda val: None, {% endmatch %} - # Error FFI converter - {%- match meth.throws_type() %} - {%- when Some(e) %} - {{ e|ffi_converter_name }}, - {%- when None %} - None, - {%- endmatch %} + {% call error_ffi_converter(meth) %} ) {%- else -%} @@ -131,17 +149,36 @@ _rust_call( {%- when Some with (return_type) %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} return {{ return_type|lift_fn }}( - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %} ) {%- when None %} - def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): + def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None: + {%- call docstring(meth, 8) %} {%- call setup_args_extra_indent(meth) %} - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %} {% endmatch %} {% endif %} {% endmacro %} + +{%- macro error_ffi_converter(func) %} + # Error FFI converter +{% match func.throws_type() %} +{%- when Some(e) %} +{%- match e -%} +{%- when Type::Enum { name, module_path } -%} + {{ e|ffi_converter_name }}, +{%- when Type::Object { name, module_path, imp } -%} + {{ e|ffi_converter_name }}__as_error, +{%- else %} + # unsupported error type! +{%- endmatch %} +{%- when None %} + None, +{%- endmatch %} +{% endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py index 24c3290ff7..1ccd6821c0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py @@ -1,3 +1,5 @@ +{%- call py::docstring_value(ci.namespace_docstring(), 0) %} + # This file was autogenerated by some hot garbage in the `uniffi` crate. # Trust me, you don't want to mess with it! @@ -13,6 +15,7 @@ # compile the rust component. The easiest way to ensure this is to bundle the Python # helpers directly inline like we're doing here. +from __future__ import annotations import os import sys import ctypes @@ -20,6 +23,9 @@ import enum import struct import contextlib import datetime +import threading +import itertools +import traceback import typing {%- if ci.has_async_fns() %} import asyncio @@ -34,20 +40,20 @@ _DEFAULT = object() {% include "RustBufferTemplate.py" %} {% include "Helpers.py" %} -{% include "PointerManager.py" %} +{% include "HandleMap.py" %} {% include "RustBufferHelper.py" %} # Contains loading, initialization code, and the FFI Function declarations. {% include "NamespaceLibraryTemplate.py" %} +# Public interface members begin here. +{{ type_helper_code }} + # Async support {%- if ci.has_async_fns() %} {%- include "Async.py" %} {%- endif %} -# Public interface members begin here. -{{ type_helper_code }} - {%- for func in ci.function_definitions() %} {%- include "TopLevelFunctionTemplate.py" %} {%- endfor %} @@ -69,6 +75,9 @@ __all__ = [ {%- for c in ci.callback_interface_definitions() %} "{{ c.name()|class_name }}", {%- endfor %} + {%- if ci.has_async_fns() %} + "uniffi_set_event_loop", + {%- endif %} ] {% import "macros.py" as py %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs index 0fcf09996f..0c23140b33 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs @@ -2,10 +2,8 @@ License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::{ - bindings::{RunScriptOptions, TargetLanguage}, - library_mode::generate_bindings, -}; +use crate::bindings::TargetLanguage; +use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{Context, Result}; use camino::Utf8Path; use std::env; @@ -34,14 +32,18 @@ pub fn run_script( args: Vec<String>, _options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; let test_helper = UniFFITestHelper::new(crate_name)?; let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; generate_bindings( &cdylib_path, None, - &[TargetLanguage::Python], + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Python], + try_format_code: false, + }, + None, &out_dir, false, )?; diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index 1f1bf8e299..04841b459c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -2,15 +2,39 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use anyhow::Result; +use anyhow::{bail, Result}; use askama::Template; +use camino::Utf8Path; use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::collections::HashMap; +use crate::bindings::ruby; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; + +pub struct RubyBindingGenerator; +impl BindingGenerator for RubyBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + ruby::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> { + if cdylib_name.is_none() { + bail!("Generate bindings for Ruby requires a cdylib, but {library_path} was given"); + } + Ok(()) + } +} const RESERVED_WORDS: &[&str] = &[ "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", @@ -57,7 +81,6 @@ pub fn canonical_name(t: &Type) -> String { Type::CallbackInterface { name, .. } => format!("CallbackInterface{name}"), Type::Timestamp => "Timestamp".into(), Type::Duration => "Duration".into(), - Type::ForeignExecutor => "ForeignExecutor".into(), // Recursive types. // These add a prefix to the name of the underlying type. // The component API definition cannot give names to recursive types, so as long as the @@ -150,20 +173,20 @@ mod filters { FfiType::UInt64 => ":uint64".to_string(), FfiType::Float32 => ":float".to_string(), FfiType::Float64 => ":double".to_string(), + FfiType::Handle => ":uint64".to_string(), FfiType::RustArcPtr(_) => ":pointer".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), + FfiType::RustCallStatus => "RustCallStatus".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), - FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"), - FfiType::ForeignExecutorCallback => { - unimplemented!("Foreign executors are not implemented") - } - FfiType::ForeignExecutorHandle => { - unimplemented!("Foreign executors are not implemented") - } - FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { - unimplemented!("Async functions are not implemented") + FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"), + // Note: this can't just be `unimplemented!()` because some of the FFI function + // definitions use references. Those FFI functions aren't actually used, so we just + // pick something that runs and makes some sense. Revisit this once the references + // are actually implemented. + FfiType::Reference(_) => ":pointer".to_string(), + FfiType::VoidPointer => ":pointer".to_string(), + FfiType::Struct(_) => { + unimplemented!("Structs are not implemented") } }) } @@ -179,7 +202,8 @@ mod filters { } // use the double-quote form to match with the other languages, and quote escapes. Literal::String(s) => format!("\"{s}\""), - Literal::Null => "nil".into(), + Literal::None => "nil".into(), + Literal::Some { inner } => literal_rb(inner)?, Literal::EmptySequence => "[]".into(), Literal::EmptyMap => "{}".into(), Literal::Enum(v, type_) => match type_ { @@ -264,7 +288,24 @@ mod filters { } Type::External { .. } => panic!("No support for external types, yet"), Type::Custom { .. } => panic!("No support for custom types, yet"), - Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), + }) + } + + pub fn check_lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> { + Ok(match type_ { + Type::Object { name, .. } => { + format!("({}.uniffi_check_lower {nm})", class_name_rb(name)?) + } + Type::Enum { .. } + | Type::Record { .. } + | Type::Optional { .. } + | Type::Sequence { .. } + | Type::Map { .. } => format!( + "RustBuffer.check_lower_{}({})", + class_name_rb(&canonical_name(type_))?, + nm + ), + _ => "".to_owned(), }) } @@ -283,7 +324,7 @@ mod filters { Type::Boolean => format!("({nm} ? 1 : 0)"), Type::String => format!("RustBuffer.allocFromString({nm})"), Type::Bytes => format!("RustBuffer.allocFromBytes({nm})"), - Type::Object { name, .. } => format!("({}._uniffi_lower {nm})", class_name_rb(name)?), + Type::Object { name, .. } => format!("({}.uniffi_lower {nm})", class_name_rb(name)?), Type::CallbackInterface { .. } => { panic!("No support for lowering callback interfaces yet") } @@ -300,7 +341,6 @@ mod filters { ), Type::External { .. } => panic!("No support for lowering external types, yet"), Type::Custom { .. } => panic!("No support for lowering custom types, yet"), - Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), }) } @@ -318,7 +358,7 @@ mod filters { Type::Boolean => format!("1 == {nm}"), Type::String => format!("{nm}.consumeIntoString"), Type::Bytes => format!("{nm}.consumeIntoBytes"), - Type::Object { name, .. } => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?), + Type::Object { name, .. } => format!("{}.uniffi_allocate({nm})", class_name_rb(name)?), Type::CallbackInterface { .. } => { panic!("No support for lifting callback interfaces, yet") } @@ -341,7 +381,6 @@ mod filters { ), Type::External { .. } => panic!("No support for lifting external types, yet"), Type::Custom { .. } => panic!("No support for lifting custom types, yet"), - Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"), }) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb index 677c5c729b..ba2caf7380 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb @@ -2,18 +2,18 @@ class {{ obj.name()|class_name_rb }} # A private helper for initializing instances of the class from a raw pointer, # bypassing any initialization logic and ensuring they are GC'd properly. - def self._uniffi_allocate(pointer) + def self.uniffi_allocate(pointer) pointer.autorelease = false inst = allocate inst.instance_variable_set :@pointer, pointer - ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) + ObjectSpace.define_finalizer(inst, uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) return inst end # A private helper for registering an object finalizer. # N.B. it's important that this does not capture a reference # to the actual instance, only its underlying pointer. - def self._uniffi_define_finalizer_by_pointer(pointer, object_id) + def self.uniffi_define_finalizer_by_pointer(pointer, object_id) Proc.new do |_id| {{ ci.namespace()|class_name_rb }}.rust_call( :{{ obj.ffi_object_free().name() }}, @@ -25,31 +25,41 @@ class {{ obj.name()|class_name_rb }} # A private helper for lowering instances into a raw pointer. # This does an explicit typecheck, because accidentally lowering a different type of # object in a place where this type is expected, could lead to memory unsafety. - def self._uniffi_lower(inst) + def self.uniffi_check_lower(inst) if not inst.is_a? self raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}" end - return inst.instance_variable_get :@pointer + end + + def uniffi_clone_pointer() + return {{ ci.namespace()|class_name_rb }}.rust_call( + :{{ obj.ffi_object_clone().name() }}, + @pointer + ) + end + + def self.uniffi_lower(inst) + return inst.uniffi_clone_pointer() end {%- match obj.primary_constructor() %} {%- when Some with (cons) %} def initialize({% call rb::arg_list_decl(cons) -%}) - {%- call rb::coerce_args_extra_indent(cons) %} + {%- call rb::setup_args_extra_indent(cons) %} pointer = {% call rb::to_ffi_call(cons) %} @pointer = pointer - ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id)) + ObjectSpace.define_finalizer(self, self.class.uniffi_define_finalizer_by_pointer(pointer, self.object_id)) end {%- when None %} {%- endmatch %} {% for cons in obj.alternate_constructors() -%} def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) - {%- call rb::coerce_args_extra_indent(cons) %} + {%- call rb::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. # Lightly yucky way to bypass the usual "initialize" logic # and just create a new instance with the required pointer. - return _uniffi_allocate({% call rb::to_ffi_call(cons) %}) + return uniffi_allocate({% call rb::to_ffi_call(cons) %}) end {% endfor %} @@ -58,15 +68,15 @@ class {{ obj.name()|class_name_rb }} {%- when Some with (return_type) -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) - {%- call rb::coerce_args_extra_indent(meth) %} - result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {%- call rb::setup_args_extra_indent(meth) %} + result = {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %} return {{ "result"|lift_rb(return_type) }} end {%- when None -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) - {%- call rb::coerce_args_extra_indent(meth) %} - {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {%- call rb::setup_args_extra_indent(meth) %} + {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %} end {% endmatch %} {% endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb index c940b31060..b5a201b248 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb @@ -2,7 +2,12 @@ class {{ rec.name()|class_name_rb }} attr_reader {% for field in rec.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %} - def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}) + def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb -}}: + {%- match field.default_value() %} + {%- when Some with(literal) %} {{ literal|literal_rb }} + {%- else %} + {%- endmatch %} + {%- if loop.last %}{% else %}, {% endif -%}{% endfor %}) {%- for field in rec.fields() %} @{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb index 8749139116..d15c0bbe76 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -163,7 +163,7 @@ class RustBufferBuilder # The Object type {{ object_name }}. def write_{{ canonical_type_name }}(obj) - pointer = {{ object_name|class_name_rb}}._uniffi_lower obj + pointer = {{ object_name|class_name_rb}}.uniffi_lower obj pack_into(8, 'Q>', pointer.address) end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb index b085dddf15..f9b0806abc 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb @@ -155,7 +155,7 @@ class RustBufferStream def read{{ canonical_type_name }} pointer = FFI::Pointer.new unpack_from 8, 'Q>' - return {{ object_name|class_name_rb }}._uniffi_allocate(pointer) + return {{ object_name|class_name_rb }}.uniffi_allocate(pointer) end {% when Type::Enum { name, module_path } -%} @@ -237,7 +237,7 @@ class RustBufferStream def read{{ canonical_type_name }} {{ rec.name()|class_name_rb }}.new( {%- for field in rec.fields() %} - read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %} + {{ field.name()|var_name_rb }}: read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %} {%- endfor %} ) end diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb index 0194c9666d..452d9831cd 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb @@ -1,6 +1,6 @@ class RustBuffer < FFI::Struct - layout :capacity, :int32, - :len, :int32, + layout :capacity, :uint64, + :len, :uint64, :data, :pointer def self.alloc(size) @@ -128,6 +128,12 @@ class RustBuffer < FFI::Struct {%- let rec = ci|get_record_definition(record_name) -%} # The Record type {{ record_name }}. + def self.check_lower_{{ canonical_type_name }}(v) + {%- for field in rec.fields() %} + {{ "v.{}"|format(field.name()|var_name_rb)|check_lower_rb(field.as_type().borrow()) }} + {%- endfor %} + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -146,6 +152,19 @@ class RustBuffer < FFI::Struct {%- let e = ci|get_enum_definition(enum_name) -%} # The Enum type {{ enum_name }}. + def self.check_lower_{{ canonical_type_name }}(v) + {%- if !e.is_flat() %} + {%- for variant in e.variants() %} + if v.{{ variant.name()|var_name_rb }}? + {%- for field in variant.fields() %} + {{ "v.{}"|format(field.name())|check_lower_rb(field.as_type().borrow()) }} + {%- endfor %} + return + end + {%- endfor %} + {%- endif %} + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -163,6 +182,12 @@ class RustBuffer < FFI::Struct {% when Type::Optional { inner_type } -%} # The Optional<T> type for {{ canonical_name(inner_type) }}. + def self.check_lower_{{ canonical_type_name }}(v) + if not v.nil? + {{ "v"|check_lower_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -179,6 +204,12 @@ class RustBuffer < FFI::Struct {% when Type::Sequence { inner_type } -%} # The Sequence<T> type for {{ canonical_name(inner_type) }}. + def self.check_lower_{{ canonical_type_name }}(v) + v.each do |item| + {{ "item"|check_lower_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) @@ -195,6 +226,13 @@ class RustBuffer < FFI::Struct {% when Type::Map { key_type: k, value_type: inner_type } -%} # The Map<T> type for {{ canonical_name(inner_type) }}. + def self.check_lower_{{ canonical_type_name }}(v) + v.each do |k, v| + {{ "k"|check_lower_rb(k.borrow()) }} + {{ "v"|check_lower_rb(inner_type.borrow()) }} + end + end + def self.alloc_from_{{ canonical_type_name }}(v) RustBuffer.allocWithBuilder do |builder| builder.write_{{ canonical_type_name }}(v) diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb index 13214cf31b..b6dce0effa 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb @@ -2,7 +2,7 @@ {%- when Some with (return_type) %} def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) - {%- call rb::coerce_args(func) %} + {%- call rb::setup_args(func) %} result = {% call rb::to_ffi_call(func) %} return {{ "result"|lift_rb(return_type) }} end @@ -10,7 +10,7 @@ end {% when None %} def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%}) - {%- call rb::coerce_args(func) %} + {%- call rb::setup_args(func) %} {% call rb::to_ffi_call(func) %} end {% endmatch %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb index 8dc3e5e613..59fa4ef4cc 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb @@ -60,14 +60,16 @@ [{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref] {%- endmacro -%} -{%- macro coerce_args(func) %} +{%- macro setup_args(func) %} {%- for arg in func.arguments() %} - {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) -}} + {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }} {% endfor -%} {%- endmacro -%} -{%- macro coerce_args_extra_indent(func) %} - {%- for arg in func.arguments() %} +{%- macro setup_args_extra_indent(meth) %} + {%- for arg in meth.arguments() %} {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }} + {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }} {%- endfor %} {%- endmacro -%} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs index 03da37d567..88f770b9dc 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs @@ -4,6 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this use crate::bindings::TargetLanguage; use crate::library_mode::generate_bindings; +use crate::BindingGeneratorDefault; use anyhow::{bail, Context, Result}; use camino::Utf8Path; use std::env; @@ -30,11 +31,21 @@ pub fn test_script_command( fixture_name: &str, script_file: &str, ) -> Result<Command> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; let test_helper = UniFFITestHelper::new(fixture_name)?; let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; - generate_bindings(&cdylib_path, None, &[TargetLanguage::Ruby], &out_dir, false)?; + generate_bindings( + &cdylib_path, + None, + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Ruby], + try_format_code: false, + }, + None, + &out_dir, + false, + )?; let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from("")); let rubypath = env::join_paths( diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs index 5d8b37e0af..dab89e0259 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs @@ -6,21 +6,25 @@ use super::CodeType; #[derive(Debug)] pub struct CallbackInterfaceCodeType { - id: String, + name: String, } impl CallbackInterfaceCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(name: String) -> Self { + Self { name } } } impl CodeType for CallbackInterfaceCodeType { fn type_label(&self) -> String { - super::SwiftCodeOracle.class_name(&self.id) + super::SwiftCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { format!("CallbackInterface{}", self.type_label()) } + + fn initialization_fn(&self) -> Option<String> { + Some(format!("uniffiCallbackInit{}", self.name)) + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs index 8e6dddf3f9..d89fdfd386 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs @@ -30,8 +30,9 @@ impl CodeType for OptionalCodeType { fn literal(&self, literal: &Literal) -> String { match literal { - Literal::Null => "nil".into(), - _ => super::SwiftCodeOracle.find(&self.inner).literal(literal), + Literal::None => "nil".into(), + Literal::Some { inner } => super::SwiftCodeOracle.find(&self.inner).literal(inner), + _ => panic!("Invalid literal for Optional type: {literal:?}"), } } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs deleted file mode 100644 index b488b004cf..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use super::CodeType; - -#[derive(Debug)] -pub struct ForeignExecutorCodeType; - -impl CodeType for ForeignExecutorCodeType { - fn type_label(&self) -> String { - // On Swift, we define a struct to represent a ForeignExecutor - "UniFfiForeignExecutor".into() - } - - fn canonical_name(&self) -> String { - "ForeignExecutor".into() - } - - fn initialization_fn(&self) -> Option<String> { - Some("uniffiInitForeignExecutor".into()) - } -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs index 0b6728ba84..3960b7aae1 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs @@ -17,7 +17,7 @@ impl ExternalCodeType { impl CodeType for ExternalCodeType { fn type_label(&self) -> String { - self.name.clone() + super::SwiftCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 12db4afc66..16c1625123 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -10,25 +10,49 @@ use std::fmt::Debug; use anyhow::{Context, Result}; use askama::Template; -use heck::{ToLowerCamelCase, ToUpperCamelCase}; +use camino::Utf8Path; +use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase}; use serde::{Deserialize, Serialize}; use super::Bindings; use crate::backend::TemplateExpression; +use crate::bindings::swift; use crate::interface::*; -use crate::BindingsConfig; +use crate::{BindingGenerator, BindingsConfig}; mod callback_interface; mod compounds; mod custom; mod enum_; -mod executor; mod external; mod miscellany; mod object; mod primitives; mod record; +pub struct SwiftBindingGenerator; +impl BindingGenerator for SwiftBindingGenerator { + type Config = Config; + + fn write_bindings( + &self, + ci: &ComponentInterface, + config: &Config, + out_dir: &Utf8Path, + try_format_code: bool, + ) -> Result<()> { + swift::write_bindings(config, ci, out_dir, try_format_code) + } + + fn check_library_path( + &self, + _library_path: &Utf8Path, + _cdylib_name: Option<&str>, + ) -> Result<()> { + Ok(()) + } +} + /// A trait tor the implementation. trait CodeType: Debug { /// The language specific label used to reference this type. This will be used in @@ -196,6 +220,8 @@ pub struct Config { ffi_module_filename: Option<String>, generate_module_map: Option<bool>, omit_argument_labels: Option<bool>, + generate_immutable_records: Option<bool>, + experimental_sendable_value_types: Option<bool>, #[serde(default)] custom_types: HashMap<String, CustomTypeConfig>, } @@ -261,6 +287,16 @@ impl Config { pub fn omit_argument_labels(&self) -> bool { self.omit_argument_labels.unwrap_or(false) } + + /// Whether to generate immutable records (`let` instead of `var`) + pub fn generate_immutable_records(&self) -> bool { + self.generate_immutable_records.unwrap_or(false) + } + + /// Whether to mark value types as 'Sendable' + pub fn experimental_sendable_value_types(&self) -> bool { + self.experimental_sendable_value_types.unwrap_or(false) + } } impl BindingsConfig for Config { @@ -400,7 +436,6 @@ pub struct SwiftWrapper<'a> { ci: &'a ComponentInterface, type_helper_code: String, type_imports: BTreeSet<String>, - has_async_fns: bool, } impl<'a> SwiftWrapper<'a> { pub fn new(config: Config, ci: &'a ComponentInterface) -> Self { @@ -412,7 +447,6 @@ impl<'a> SwiftWrapper<'a> { ci, type_helper_code, type_imports, - has_async_fns: ci.has_async_fns(), } } @@ -425,10 +459,6 @@ impl<'a> SwiftWrapper<'a> { .iter_types() .map(|t| SwiftCodeOracle.find(t)) .filter_map(|ct| ct.initialization_fn()) - .chain( - self.has_async_fns - .then(|| "uniffiInitContinuationCallback".into()), - ) .collect() } } @@ -464,12 +494,11 @@ impl SwiftCodeOracle { Type::Duration => Box::new(miscellany::DurationCodeType), Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)), - Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)), + Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)), Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)), Type::CallbackInterface { name, .. } => { Box::new(callback_interface::CallbackInterfaceCodeType::new(name)) } - Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType), Type::Optional { inner_type } => { Box::new(compounds::OptionalCodeType::new(*inner_type)) } @@ -509,7 +538,22 @@ impl SwiftCodeOracle { nm.to_string().to_lower_camel_case() } - fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String { + /// Get the idiomatic Swift rendering of an FFI callback function name + fn ffi_callback_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + /// Get the idiomatic Swift rendering of an FFI struct name + fn ffi_struct_name(&self, nm: &str) -> String { + format!("Uniffi{}", nm.to_upper_camel_case()) + } + + /// Get the idiomatic Swift rendering of an if guard name + fn if_guard_name(&self, nm: &str) -> String { + format!("UNIFFI_FFIDEF_{}", nm.to_shouty_snake_case()) + } + + fn ffi_type_label(&self, ffi_type: &FfiType) -> String { match ffi_type { FfiType::Int8 => "Int8".into(), FfiType::UInt8 => "UInt8".into(), @@ -521,40 +565,74 @@ impl SwiftCodeOracle { FfiType::UInt64 => "UInt64".into(), FfiType::Float32 => "Float".into(), FfiType::Float64 => "Double".into(), + FfiType::Handle => "UInt64".into(), FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::RustCallStatus => "RustCallStatus".into(), FfiType::ForeignBytes => "ForeignBytes".into(), - FfiType::ForeignCallback => "ForeignCallback".into(), - FfiType::ForeignExecutorHandle => "Int".into(), - FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), - FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "UnsafeMutableRawPointer".into() + // Note: @escaping is required for Swift versions before 5.7 for callbacks passed into + // async functions. Swift 5.7 and later does not require it. We should probably remove + // it once we upgrade our minimum requirement to 5.7 or later. + FfiType::Callback(name) => format!("@escaping {}", self.ffi_callback_name(name)), + FfiType::Struct(name) => self.ffi_struct_name(name), + FfiType::Reference(inner) => { + format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner)) } + FfiType::VoidPointer => "UnsafeMutableRawPointer".into(), } } - fn ffi_type_label(&self, ffi_type: &FfiType) -> String { - match ffi_type { - FfiType::ForeignCallback - | FfiType::ForeignExecutorCallback - | FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { - format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) - } - _ => self.ffi_type_label_raw(ffi_type), + /// Default values for FFI types + /// + /// Used to set a default return value when returning an error + fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 + | FfiType::Int8 + | FfiType::UInt16 + | FfiType::Int16 + | FfiType::UInt32 + | FfiType::Int32 + | FfiType::UInt64 + | FfiType::Int64 => "0".to_owned(), + FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(), + FfiType::RustArcPtr(_) => "nil".to_owned(), + FfiType::RustBuffer(_) => "RustBuffer.empty()".to_owned(), + _ => unimplemented!("FFI return type: {t:?}"), + }, + // When we need to use a value for void returns, we use a `u8` placeholder + None => "0".to_owned(), } } fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String { - self.ffi_type_label_raw(ffi_type) + self.ffi_type_label(ffi_type) + } + + /// Get the name of the protocol and class name for an object. + /// + /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that. + /// Otherwise, the class name is the object name and the protocol name is derived from that. + /// + /// This split determines what types `FfiConverter.lower()` inputs. If we support callback + /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower + /// only lowers the concrete class. + fn object_names(&self, obj: &Object) -> (String, String) { + let class_name = self.class_name(obj.name()); + if obj.has_callback_interface() { + let impl_name = format!("{class_name}Impl"); + (class_name, impl_name) + } else { + (format!("{class_name}Protocol"), class_name) + } } } pub mod filters { use super::*; pub use crate::backend::filters::*; + use uniffi_meta::LiteralMetadata; fn oracle() -> &'static SwiftCodeOracle { &SwiftCodeOracle @@ -564,6 +642,13 @@ pub mod filters { Ok(oracle().find(&as_type.as_type()).type_label()) } + pub fn return_type_name(as_type: Option<&impl AsType>) -> Result<String, askama::Error> { + Ok(match as_type { + Some(as_type) => oracle().find(&as_type.as_type()).type_label(), + None => "()".to_owned(), + }) + } + pub fn canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> { Ok(oracle().find(&as_type.as_type()).canonical_name()) } @@ -572,6 +657,15 @@ pub mod filters { Ok(oracle().find(&as_type.as_type()).ffi_converter_name()) } + pub fn ffi_error_converter_name(as_type: &impl AsType) -> Result<String, askama::Error> { + // special handling for types used as errors. + let mut name = oracle().find(&as_type.as_type()).ffi_converter_name(); + if matches!(&as_type.as_type(), Type::Object { .. }) { + name.push_str("__as_error") + } + Ok(name) + } + pub fn lower_fn(as_type: &impl AsType) -> Result<String, askama::Error> { Ok(oracle().find(&as_type.as_type()).lower()) } @@ -595,6 +689,16 @@ pub mod filters { Ok(oracle().find(&as_type.as_type()).literal(literal)) } + // Get the idiomatic Swift rendering of an individual enum variant's discriminant + pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> { + let literal = e.variant_discr(*index).expect("invalid index"); + match literal { + LiteralMetadata::UInt(v, _, _) => Ok(v.to_string()), + LiteralMetadata::Int(v, _, _) => Ok(v.to_string()), + _ => unreachable!("expected an UInt!"), + } + } + /// Get the Swift type for an FFIType pub fn ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> { Ok(oracle().ffi_type_label(ffi_type)) @@ -604,6 +708,10 @@ pub mod filters { Ok(oracle().ffi_canonical_name(ffi_type)) } + pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> { + Ok(oracle().ffi_default_value(return_type.as_ref())) + } + /// Like `ffi_type_name`, but used in `BridgingHeaderTemplate.h` which uses a slightly different /// names. pub fn header_ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> { @@ -618,18 +726,17 @@ pub mod filters { FfiType::UInt64 => "uint64_t".into(), FfiType::Float32 => "float".into(), FfiType::Float64 => "double".into(), + FfiType::Handle => "uint64_t".into(), FfiType::RustArcPtr(_) => "void*_Nonnull".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), + FfiType::RustCallStatus => "RustCallStatus".into(), FfiType::ForeignBytes => "ForeignBytes".into(), - FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), - FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(), - FfiType::ForeignExecutorHandle => "size_t".into(), - FfiType::RustFutureContinuationCallback => { - "UniFfiRustFutureContinuation _Nonnull".into() - } - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "void* _Nonnull".into() + FfiType::Callback(name) => { + format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name)) } + FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name), + FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?), + FfiType::VoidPointer => "void* _Nonnull".into(), }) } @@ -664,6 +771,30 @@ pub mod filters { Ok(oracle().enum_variant_name(nm)) } + /// Get the idiomatic Swift rendering of an FFI callback function name + pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().ffi_callback_name(nm)) + } + + /// Get the idiomatic Swift rendering of an FFI struct name + pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().ffi_struct_name(nm)) + } + + /// Get the idiomatic Swift rendering of an if guard name + pub fn if_guard_name(nm: &str) -> Result<String, askama::Error> { + Ok(oracle().if_guard_name(nm)) + } + + /// Get the idiomatic Swift rendering of docstring + pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> { + let middle = textwrap::indent(&textwrap::dedent(docstring), " * "); + let wrapped = format!("/**\n{middle}\n */"); + + let spaces = usize::try_from(*spaces).unwrap_or_default(); + Ok(textwrap::indent(&wrapped, &" ".repeat(spaces))) + } + pub fn error_handler(result: &ResultType) -> Result<String, askama::Error> { Ok(match &result.throws_type { Some(t) => format!("{}.lift", ffi_converter_name(t)?), @@ -685,4 +816,8 @@ pub mod filters { } )) } + + pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> { + Ok(SwiftCodeOracle.object_names(obj)) + } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs index ea140c998d..d4497a7b19 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs @@ -3,24 +3,32 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::CodeType; +use crate::interface::ObjectImpl; #[derive(Debug)] pub struct ObjectCodeType { - id: String, + name: String, + imp: ObjectImpl, } impl ObjectCodeType { - pub fn new(id: String) -> Self { - Self { id } + pub fn new(name: String, imp: ObjectImpl) -> Self { + Self { name, imp } } } impl CodeType for ObjectCodeType { fn type_label(&self) -> String { - super::SwiftCodeOracle.class_name(&self.id) + super::SwiftCodeOracle.class_name(&self.name) } fn canonical_name(&self) -> String { - format!("Type{}", self.id) + format!("Type{}", self.name) + } + + fn initialization_fn(&self) -> Option<String> { + self.imp + .has_callback_interface() + .then(|| format!("uniffiCallbackInit{}", self.name)) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs index 86424658a3..e0c670520e 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs @@ -9,7 +9,11 @@ use paste::paste; fn render_literal(literal: &Literal) -> String { fn typed_number(type_: &Type, num_str: String) -> String { - match type_ { + let unwrapped_type = match type_ { + Type::Optional { inner_type } => inner_type, + t => t, + }; + match unwrapped_type { // special case Int32. Type::Int32 => num_str, // otherwise use constructor e.g. UInt8(x) @@ -29,7 +33,7 @@ fn render_literal(literal: &Literal) -> String { super::SwiftCodeOracle.find(type_).type_label() ) } - _ => panic!("Unexpected literal: {num_str} is not a number"), + _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"), } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift index 695208861d..e16f3108e1 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -1,11 +1,13 @@ private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 +fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>() + fileprivate func uniffiRustCallAsync<F, T>( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), + completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F, + freeFunc: (UInt64) -> (), liftFunc: (F) throws -> T, errorHandler: ((RustBuffer) throws -> Error)? ) async throws -> T { @@ -19,7 +21,11 @@ fileprivate func uniffiRustCallAsync<F, T>( var pollResult: Int8; repeat { pollResult = await withUnsafeContinuation { - pollFunc(rustFuture, ContinuationHolder($0).toOpaque()) + pollFunc( + rustFuture, + uniffiFutureContinuationCallback, + uniffiContinuationHandleMap.insert(obj: $0) + ) } } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY @@ -31,32 +37,80 @@ fileprivate func uniffiRustCallAsync<F, T>( // Callback handlers for an async calls. These are invoked by Rust when the future is ready. They // lift the return value or error and resume the suspended function. -fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) { - ContinuationHolder.fromOpaque(ptr).resume(pollResult) +fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) { + if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) { + continuation.resume(returning: pollResult) + } else { + print("uniffiFutureContinuationCallback invalid handle") + } } -// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across -// the FFI -fileprivate class ContinuationHolder { - let continuation: UnsafeContinuation<Int8, Never> - - init(_ continuation: UnsafeContinuation<Int8, Never>) { - self.continuation = continuation +{%- if ci.has_async_callback_interface_definition() %} +private func uniffiTraitInterfaceCallAsync<T>( + makeCall: @escaping () async throws -> T, + handleSuccess: @escaping (T) -> (), + handleError: @escaping (Int8, RustBuffer) -> () +) -> UniffiForeignFuture { + let task = Task { + do { + handleSuccess(try await makeCall()) + } catch { + handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error))) + } } + let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task) + return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree) - func resume(_ pollResult: Int8) { - self.continuation.resume(returning: pollResult) - } +} - func toOpaque() -> UnsafeMutableRawPointer { - return Unmanaged<ContinuationHolder>.passRetained(self).toOpaque() +private func uniffiTraitInterfaceCallAsyncWithError<T, E>( + makeCall: @escaping () async throws -> T, + handleSuccess: @escaping (T) -> (), + handleError: @escaping (Int8, RustBuffer) -> (), + lowerError: @escaping (E) -> RustBuffer +) -> UniffiForeignFuture { + let task = Task { + do { + handleSuccess(try await makeCall()) + } catch let error as E { + handleError(CALL_ERROR, lowerError(error)) + } catch { + handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error))) + } } + let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task) + return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree) +} + +// Borrow the callback handle map implementation to store foreign future handles +// TODO: consolidate the handle-map code (https://github.com/mozilla/uniffi-rs/pull/1823) +fileprivate var UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = UniffiHandleMap<UniffiForeignFutureTask>() + +// Protocol for tasks that handle foreign futures. +// +// Defining a protocol allows all tasks to be stored in the same handle map. This can't be done +// with the task object itself, since has generic parameters. +protocol UniffiForeignFutureTask { + func cancel() +} + +extension Task: UniffiForeignFutureTask {} - static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder { - return Unmanaged<ContinuationHolder>.fromOpaque(ptr).takeRetainedValue() +private func uniffiForeignFutureFree(handle: UInt64) { + do { + let task = try UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle: handle) + // Set the cancellation flag on the task. If it's still running, the code can check the + // cancellation flag or call `Task.checkCancellation()`. If the task has completed, this is + // a no-op. + task.cancel() + } catch { + print("uniffiForeignFutureFree: handle missing from handlemap") } } -fileprivate func uniffiInitContinuationCallback() { - {{ ci.ffi_rust_future_continuation_callback_set().name() }}(uniffiFutureContinuationCallback) +// For testing +public func uniffiForeignFutureHandleCount{{ ci.namespace()|class_name }}() -> Int { + UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.count } + +{%- endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h index 87698e359f..89d98594d3 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h @@ -24,25 +24,11 @@ typedef struct RustBuffer { - int32_t capacity; - int32_t len; + uint64_t capacity; + uint64_t len; uint8_t *_Nullable data; } RustBuffer; -typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull); - -// Task defined in Rust that Swift executes -typedef void (*UniFfiRustTaskCallback)(const void * _Nullable, int8_t); - -// Callback to execute Rust tasks using a Swift Task -// -// Args: -// executor: ForeignExecutor lowered into a size_t value -// delay: Delay in MS -// task: UniFfiRustTaskCallback to call -// task_data: data to pass the task callback -typedef int8_t (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable); - typedef struct ForeignBytes { int32_t len; @@ -59,11 +45,29 @@ typedef struct RustCallStatus { // ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ #endif // def UNIFFI_SHARED_H -// Continuation callback for UniFFI Futures -typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); - -// Scaffolding functions -{%- for func in ci.iter_ffi_function_definitions() %} +{%- for def in ci.ffi_definitions() %} +#ifndef {{ def.name()|if_guard_name }} +#define {{ def.name()|if_guard_name }} +{%- match def %} +{% when FfiDefinition::CallbackFunction(callback) %} +typedef + {%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|header_ffi_type_name }} {% when None %} void {% endmatch -%} + (*{{ callback.name()|ffi_callback_name }})( + {%- for arg in callback.arguments() -%} + {{ arg.type_().borrow()|header_ffi_type_name }} + {%- if !loop.last || callback.has_rust_call_status_arg() %}, {% endif %} + {%- endfor -%} + {%- if callback.has_rust_call_status_arg() %} + RustCallStatus *_Nonnull uniffiCallStatus + {%- endif %} + ); +{% when FfiDefinition::Struct(struct) %} +typedef struct {{ struct.name()|ffi_struct_name }} { + {%- for field in struct.fields() %} + {{ field.type_().borrow()|header_ffi_type_name }} {{ field.name()|var_name }}; + {%- endfor %} +} {{ struct.name()|ffi_struct_name }}; +{% when FfiDefinition::Function(func) %} {% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}( {%- if func.arguments().len() > 0 %} {%- for arg in func.arguments() %} @@ -74,6 +78,8 @@ typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t); {%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %} {% endif %} ); +{%- endmatch %} +#endif {%- endfor %} {% import "macros.swift" as swift %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift new file mode 100644 index 0000000000..74ee372642 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift @@ -0,0 +1,113 @@ +{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} +{%- let trait_impl=format!("UniffiCallbackInterface{}", name) %} + +// Put the implementation in a struct so we don't pollute the top-level namespace +fileprivate struct {{ trait_impl }} { + + // Create the VTable using a series of closures. + // Swift automatically converts these into C callback functions. + static var vtable: {{ vtable|ffi_type_name }} = {{ vtable|ffi_type_name }}( + {%- for (ffi_callback, meth) in vtable_methods %} + {{ meth.name()|fn_name }}: { ( + {%- for arg in ffi_callback.arguments() %} + {{ arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name }}{% if !loop.last || ffi_callback.has_rust_call_status_arg() %},{% endif %} + {%- endfor -%} + {%- if ffi_callback.has_rust_call_status_arg() %} + uniffiCallStatus: UnsafeMutablePointer<RustCallStatus> + {%- endif %} + ) in + let makeCall = { + () {% if meth.is_async() %}async {% endif %}throws -> {% match meth.return_type() %}{% when Some(t) %}{{ t|type_name }}{% when None %}(){% endmatch %} in + guard let uniffiObj = try? {{ ffi_converter_name }}.handleMap.get(handle: uniffiHandle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return {% if meth.throws() %}try {% endif %}{% if meth.is_async() %}await {% endif %}uniffiObj.{{ meth.name()|fn_name }}( + {%- for arg in meth.arguments() %} + {% if !config.omit_argument_labels() %} {{ arg.name()|arg_name }}: {% endif %}try {{ arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) + } + {%- if !meth.is_async() %} + + {% match meth.return_type() %} + {%- when Some(t) %} + let writeReturn = { uniffiOutReturn.pointee = {{ t|lower_fn }}($0) } + {%- when None %} + let writeReturn = { () } + {%- endmatch %} + + {%- match meth.throws_type() %} + {%- when None %} + uniffiTraitInterfaceCall( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn + ) + {%- when Some(error_type) %} + uniffiTraitInterfaceCallWithError( + callStatus: uniffiCallStatus, + makeCall: makeCall, + writeReturn: writeReturn, + lowerError: {{ error_type|lower_fn }} + ) + {%- endmatch %} + {%- else %} + + let uniffiHandleSuccess = { (returnValue: {{ meth.return_type()|return_type_name }}) in + uniffiFutureCallback( + uniffiCallbackData, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + returnValue: {{ return_type|lower_fn }}(returnValue), + {%- when None %} + {%- endmatch %} + callStatus: RustCallStatus() + ) + ) + } + let uniffiHandleError = { (statusCode, errorBuf) in + uniffiFutureCallback( + uniffiCallbackData, + {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}( + {%- match meth.return_type() %} + {%- when Some(return_type) %} + returnValue: {{ meth.return_type().map(FfiType::from)|ffi_default_value }}, + {%- when None %} + {%- endmatch %} + callStatus: RustCallStatus(code: statusCode, errorBuf: errorBuf) + ) + ) + } + + {%- match meth.throws_type() %} + {%- when None %} + let uniffiForeignFuture = uniffiTraitInterfaceCallAsync( + makeCall: makeCall, + handleSuccess: uniffiHandleSuccess, + handleError: uniffiHandleError + ) + {%- when Some(error_type) %} + let uniffiForeignFuture = uniffiTraitInterfaceCallAsyncWithError( + makeCall: makeCall, + handleSuccess: uniffiHandleSuccess, + handleError: uniffiHandleError, + lowerError: {{ error_type|lower_fn }} + ) + {%- endmatch %} + uniffiOutReturn.pointee = uniffiForeignFuture + {%- endif %} + }, + {%- endfor %} + uniffiFree: { (uniffiHandle: UInt64) -> () in + let result = try? {{ ffi_converter_name }}.handleMap.remove(handle: uniffiHandle) + if result == nil { + print("Uniffi callback interface {{ name }}: handle missing in uniffiFree") + } + } + ) +} + +private func {{ callback_init }}() { + {{ ffi_init_callback.name() }}(&{{ trait_impl }}.vtable) +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift index 9ae62d1667..5863c2ad41 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift @@ -1,60 +1,3 @@ -fileprivate extension NSLock { - func withLock<T>(f: () throws -> T) rethrows -> T { - self.lock() - defer { self.unlock() } - return try f() - } -} - -fileprivate typealias UniFFICallbackHandle = UInt64 -fileprivate class UniFFICallbackHandleMap<T> { - private var leftMap: [UniFFICallbackHandle: T] = [:] - private var counter: [UniFFICallbackHandle: UInt64] = [:] - private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:] - - private let lock = NSLock() - private var currentHandle: UniFFICallbackHandle = 0 - private let stride: UniFFICallbackHandle = 1 - - func insert(obj: T) -> UniFFICallbackHandle { - lock.withLock { - let id = ObjectIdentifier(obj as AnyObject) - let handle = rightMap[id] ?? { - currentHandle += stride - let handle = currentHandle - leftMap[handle] = obj - rightMap[id] = handle - return handle - }() - counter[handle] = (counter[handle] ?? 0) + 1 - return handle - } - } - - func get(handle: UniFFICallbackHandle) -> T? { - lock.withLock { - leftMap[handle] - } - } - - func delete(handle: UniFFICallbackHandle) { - remove(handle: handle) - } - - @discardableResult - func remove(handle: UniFFICallbackHandle) -> T? { - lock.withLock { - defer { counter[handle] = (counter[handle] ?? 1) - 1 } - guard counter[handle] == 1 else { return leftMap[handle] } - let obj = leftMap.removeValue(forKey: handle) - if let obj = obj { - rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) - } - return obj - } - } -} - // Magic number for the Rust proxy to call using the same mechanism as every other method, // to free the callback once it's dropped by Rust. private let IDX_CALLBACK_FREE: Int32 = 0 diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index aec8ded930..7aa1cca9b2 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -1,150 +1,39 @@ {%- let cbi = ci|get_callback_interface_definition(name) %} -{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %} -{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %} - -// Declaration and FfiConverters for {{ type_name }} Callback Interface - -public protocol {{ type_name }} : AnyObject { - {% for meth in cbi.methods() -%} - func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%} - {%- match meth.return_type() -%} - {%- when Some with (return_type) %} -> {{ return_type|type_name -}} - {%- else -%} - {%- endmatch %} - {% endfor %} -} - -// The ForeignCallback that is passed to Rust. -fileprivate let {{ foreign_callback }} : ForeignCallback = - { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer<UInt8>, argsLen: Int32, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in - {% for meth in cbi.methods() -%} - {%- let method_name = format!("invoke_{}", meth.name())|fn_name %} - - func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer<UInt8>, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer<RustBuffer>) throws -> Int32 { - {%- if meth.arguments().len() > 0 %} - var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen))) - {%- endif %} - - {%- match meth.return_type() %} - {%- when Some(return_type) %} - func makeCall() throws -> Int32 { - let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - var writer = [UInt8]() - {{ return_type|write_fn }}(result, into: &writer) - out_buf.pointee = RustBuffer(bytes: writer) - return UNIFFI_CALLBACK_SUCCESS - } - {%- when None %} - func makeCall() throws -> Int32 { - try swiftCallbackInterface.{{ meth.name()|fn_name }}( - {% for arg in meth.arguments() -%} - {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader) - {%- if !loop.last %}, {% endif %} - {% endfor -%} - ) - return UNIFFI_CALLBACK_SUCCESS - } - {%- endmatch %} - - {%- match meth.throws_type() %} - {%- when None %} - return try makeCall() - {%- when Some(error_type) %} - do { - return try makeCall() - } catch let error as {{ error_type|type_name }} { - out_buf.pointee = {{ error_type|lower_fn }}(error) - return UNIFFI_CALLBACK_ERROR - } - {%- endmatch %} - } - {%- endfor %} - - - switch method { - case IDX_CALLBACK_FREE: - {{ ffi_converter_name }}.drop(handle: handle) - // Sucessful return - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return UNIFFI_CALLBACK_SUCCESS - {% for meth in cbi.methods() -%} - {% let method_name = format!("invoke_{}", meth.name())|fn_name -%} - case {{ loop.index }}: - let cb: {{ cbi|type_name }} - do { - cb = try {{ ffi_converter_name }}.lift(handle) - } catch { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle") - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - do { - return try {{ method_name }}(cb, argsData, argsLen, out_buf) - } catch let error { - out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } - {% endfor %} - // This should never happen, because an out of bounds method index won't - // ever be used. Once we can catch errors, we should return an InternalError. - // https://github.com/mozilla/uniffi-rs/issues/351 - default: - // An unexpected error happened. - // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` - return UNIFFI_CALLBACK_UNEXPECTED_ERROR - } -} +{%- let callback_handler = format!("uniffiCallbackHandler{}", name) %} +{%- let callback_init = format!("uniffiCallbackInit{}", name) %} +{%- let methods = cbi.methods() %} +{%- let protocol_name = type_name.clone() %} +{%- let protocol_docstring = cbi.docstring() %} +{%- let vtable = cbi.vtable() %} +{%- let vtable_methods = cbi.vtable_methods() %} +{%- let ffi_init_callback = cbi.ffi_init_callback() %} + +{% include "Protocol.swift" %} +{% include "CallbackInterfaceImpl.swift" %} // FfiConverter protocol for callback interfaces fileprivate struct {{ ffi_converter_name }} { - private static let initCallbackOnce: () = { - // Swift ensures this initializer code will once run once, even when accessed by multiple threads. - try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in - {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err) - } - }() - - private static func ensureCallbackinitialized() { - _ = initCallbackOnce - } - - static func drop(handle: UniFFICallbackHandle) { - handleMap.remove(handle: handle) - } - - private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() + fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>() } extension {{ ffi_converter_name }} : FfiConverter { typealias SwiftType = {{ type_name }} - // We can use Handle as the FfiType because it's a typealias to UInt64 - typealias FfiType = UniFFICallbackHandle + typealias FfiType = UInt64 - public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType { - ensureCallbackinitialized(); - guard let callback = handleMap.get(handle: handle) else { - throw UniffiInternalError.unexpectedStaleHandle - } - return callback + public static func lift(_ handle: UInt64) throws -> SwiftType { + try handleMap.get(handle: handle) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - ensureCallbackinitialized(); - let handle: UniFFICallbackHandle = try readInt(&buf) + let handle: UInt64 = try readInt(&buf) return try lift(handle) } - public static func lower(_ v: SwiftType) -> UniFFICallbackHandle { - ensureCallbackinitialized(); + public static func lower(_ v: SwiftType) -> UInt64 { return handleMap.insert(obj: v) } public static func write(_ v: SwiftType, into buf: inout [UInt8]) { - ensureCallbackinitialized(); writeInt(&buf, lower(v)) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift index 99f45290cc..1d8b3cf500 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift @@ -1,10 +1,26 @@ // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +{%- call swift::docstring(e, 0) %} +{% match e.variant_discr_type() %} +{% when None %} public enum {{ type_name }} { {% for variant in e.variants() %} - case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {%- call swift::docstring(variant, 4) %} + case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}( + {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %} + ){% endif -%} {% endfor %} } +{% when Some with (variant_discr_type) %} +public enum {{ type_name }} : {{ variant_discr_type|type_name }} { + {% for variant in e.variants() %} + {%- call swift::docstring(variant, 4) %} + case {{ variant.name()|enum_variant_swift_quoted }} = {{ e|variant_discr_literal(loop.index0) }}{% if variant.fields().len() > 0 %}( + {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %} + ){% endif -%} + {% endfor %} +} +{% endmatch %} public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { typealias SwiftType = {{ type_name }} @@ -15,7 +31,11 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { {% for variant in e.variants() %} case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift_quoted }}{% if variant.has_fields() %}( {%- for field in variant.fields() %} + {%- if variant.has_nameless_fields() -%} + try {{ field|read_fn }}(from: &buf) + {%- else -%} {{ field.name()|arg_name }}: try {{ field|read_fn }}(from: &buf) + {%- endif -%} {%- if !loop.last %}, {% endif %} {%- endfor %} ){%- endif %} @@ -28,10 +48,10 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { switch value { {% for variant in e.variants() %} {% if variant.has_fields() %} - case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): + case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{%- call swift::field_name(field, loop.index) -%}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}): writeInt(&buf, Int32({{ loop.index }})) {% for field in variant.fields() -%} - {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf) + {{ field|write_fn }}({% call swift::field_name(field, loop.index) %}, into: &buf) {% endfor -%} {% else %} case .{{ variant.name()|enum_variant_swift_quoted }}: @@ -55,5 +75,6 @@ public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuff } {% if !contains_object_references %} +{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %} extension {{ type_name }}: Equatable, Hashable {} {% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift index 786091395b..0702c477e9 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift @@ -1,21 +1,21 @@ +{%- call swift::docstring(e, 0) %} public enum {{ type_name }} { {% if e.is_flat() %} {% for variant in e.variants() %} - // Simple error enums only carry a message + {%- call swift::docstring(variant, 4) %} case {{ variant.name()|class_name }}(message: String) {% endfor %} {%- else %} {% for variant in e.variants() %} - case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%} + {%- call swift::docstring(variant, 4) %} + case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}( + {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %} + ){% endif -%} {% endfor %} {%- endif %} - - fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error { - return try {{ ffi_converter_name }}.lift(error) - } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift deleted file mode 100644 index 167e4c7546..0000000000 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift +++ /dev/null @@ -1,69 +0,0 @@ -private let UNIFFI_RUST_TASK_CALLBACK_SUCCESS: Int8 = 0 -private let UNIFFI_RUST_TASK_CALLBACK_CANCELLED: Int8 = 1 -private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS: Int8 = 0 -private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED: Int8 = 1 -private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR: Int8 = 2 - -// Encapsulates an executor that can run Rust tasks -// -// On Swift, `Task.detached` can handle this we just need to know what priority to send it. -public struct UniFfiForeignExecutor { - var priority: TaskPriority - - public init(priority: TaskPriority) { - self.priority = priority - } - - public init() { - self.priority = Task.currentPriority - } -} - -fileprivate struct FfiConverterForeignExecutor: FfiConverter { - typealias SwiftType = UniFfiForeignExecutor - // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8. - // let's use `Int`, which is equivalent to `size_t` - typealias FfiType = Int - - public static func lift(_ value: FfiType) throws -> SwiftType { - UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value))) - } - public static func lower(_ value: SwiftType) -> FfiType { - numericCast(value.priority.rawValue) - } - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - fatalError("FfiConverterForeignExecutor.read not implemented yet") - } - public static func write(_ value: SwiftType, into buf: inout [UInt8]) { - fatalError("FfiConverterForeignExecutor.read not implemented yet") - } -} - - -fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) -> Int8 { - if let rustTask = rustTask { - let executor = try! FfiConverterForeignExecutor.lift(executorHandle) - Task.detached(priority: executor.priority) { - if delayMs != 0 { - let nanoseconds: UInt64 = numericCast(delayMs * 1000000) - try! await Task.sleep(nanoseconds: nanoseconds) - } - rustTask(taskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS) - } - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - } else { - // When rustTask is null, we should drop the foreign executor. - // However, since its just a value type, we don't need to do anything here. - return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS - } -} - -fileprivate func uniffiInitForeignExecutor() { - {%- match ci.ffi_foreign_executor_callback_set() %} - {%- when Some with (fn) %} - {{ fn.name() }}(uniffiForeignExecutorCallback) - {%- when None %} - {#- No foreign executor, we don't set anything #} - {% endmatch %} -} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift new file mode 100644 index 0000000000..6de9f085d6 --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift @@ -0,0 +1,40 @@ +fileprivate class UniffiHandleMap<T> { + private var map: [UInt64: T] = [:] + private let lock = NSLock() + private var currentHandle: UInt64 = 1 + + func insert(obj: T) -> UInt64 { + lock.withLock { + let handle = currentHandle + currentHandle += 1 + map[handle] = obj + return handle + } + } + + func get(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + @discardableResult + func remove(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map.removeValue(forKey: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + var count: Int { + get { + map.count + } + } +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift index a34b128e23..cfddf7b313 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift @@ -26,9 +26,17 @@ fileprivate enum UniffiInternalError: LocalizedError { } } +fileprivate extension NSLock { + func withLock<T>(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + fileprivate let CALL_SUCCESS: Int8 = 0 fileprivate let CALL_ERROR: Int8 = 1 -fileprivate let CALL_PANIC: Int8 = 2 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 fileprivate let CALL_CANCELLED: Int8 = 3 fileprivate extension RustCallStatus { @@ -81,7 +89,7 @@ private func uniffiCheckCallStatus( throw UniffiInternalError.unexpectedRustCallError } - case CALL_PANIC: + case CALL_UNEXPECTED_ERROR: // When the rust code sees a panic, it tries to construct a RustBuffer // with the message. But if that code panics, then it just sends back // an empty buffer. @@ -93,9 +101,39 @@ private func uniffiCheckCallStatus( } case CALL_CANCELLED: - throw CancellationError() + fatalError("Cancellation not supported yet") default: throw UniffiInternalError.unexpectedRustCallStatusCode } } + +private func uniffiTraitInterfaceCall<T>( + callStatus: UnsafeMutablePointer<RustCallStatus>, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError<T, E>( + callStatus: UnsafeMutablePointer<RustCallStatus>, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error)) + } +} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index 57a77ca6df..0c28bc4c09 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -1,104 +1,146 @@ {%- let obj = ci|get_object_definition(name) %} -public protocol {{ obj.name() }}Protocol { - {% for meth in obj.methods() -%} - func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) %} {% call swift::throws(meth) -%} - {%- match meth.return_type() -%} - {%- when Some with (return_type) %} -> {{ return_type|type_name -}} - {%- else -%} - {%- endmatch %} - {% endfor %} -} - -public class {{ type_name }}: {{ obj.name() }}Protocol { - fileprivate let pointer: UnsafeMutableRawPointer +{%- let (protocol_name, impl_class_name) = obj|object_names %} +{%- let methods = obj.methods() %} +{%- let protocol_docstring = obj.docstring() %} + +{%- let is_error = ci.is_name_used_as_error(name) %} + +{% include "Protocol.swift" %} + +{%- call swift::docstring(obj, 0) %} +open class {{ impl_class_name }}: + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + CustomStringConvertible, + {%- when UniffiTrait::Debug { fmt } %} + CustomDebugStringConvertible, + {%- when UniffiTrait::Eq { eq, ne } %} + Equatable, + {%- when UniffiTrait::Hash { hash } %} + Hashable, + {%- else %} + {%- endmatch %} + {%- endfor %} + {%- if is_error %} + Error, + {% endif %} + {{ protocol_name }} { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } // TODO: We'd like this to be `private` but for Swifty reasons, // we can't implement `FfiConverter` without making this `required` and we can't // make it `required` without making it `public`. - required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { self.pointer = pointer } + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { {{ obj.ffi_object_clone().name() }}(self.pointer, $0) } + } + {%- match obj.primary_constructor() %} {%- when Some with (cons) %} - public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { - self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) - } + {%- call swift::ctor_decl(cons, 4) %} {%- when None %} + // No primary constructor declared for this class. {%- endmatch %} deinit { + guard let pointer = pointer else { + return + } + try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) } } {% for cons in obj.alternate_constructors() %} - - public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} { - return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) - } - + {%- call swift::func_decl("public static func", cons, 4) %} {% endfor %} - {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #} {% for meth in obj.methods() -%} - {%- if meth.is_async() %} - - public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { - return {% call swift::try(meth) %} await uniffiRustCallAsync( - rustFutureFunc: { - {{ meth.ffi_func().name() }}( - self.pointer - {%- for arg in meth.arguments() -%} - , - {{ arg|lower_fn }}({{ arg.name()|var_name }}) - {%- endfor %} - ) - }, - pollFunc: {{ meth.ffi_rust_future_poll(ci) }}, - completeFunc: {{ meth.ffi_rust_future_complete(ci) }}, - freeFunc: {{ meth.ffi_rust_future_free(ci) }}, - {%- match meth.return_type() %} - {%- when Some(return_type) %} - liftFunc: {{ return_type|lift_fn }}, - {%- when None %} - liftFunc: { $0 }, - {%- endmatch %} - {%- match meth.throws_type() %} - {%- when Some with (e) %} - errorHandler: {{ e|ffi_converter_name }}.lift - {%- else %} - errorHandler: nil - {% endmatch %} + {%- call swift::func_decl("open func", meth, 4) %} + {% endfor %} + + {%- for tm in obj.uniffi_traits() %} + {%- match tm %} + {%- when UniffiTrait::Display { fmt } %} + open var description: String { + return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(fmt) %} ) } - - {% else -%} - - {%- match meth.return_type() -%} - - {%- when Some with (return_type) %} - - public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { - return {% call swift::try(meth) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {%- when UniffiTrait::Debug { fmt } %} + open var debugDescription: String { + return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(fmt) %} ) } - - {%- when None %} - - public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {%- when UniffiTrait::Eq { eq, ne } %} + public static func == (self: {{ impl_class_name }}, other: {{ impl_class_name }}) -> Bool { + return {% call swift::try(eq) %} {{ eq.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(eq) %} + ) + } + {%- when UniffiTrait::Hash { hash } %} + open func hash(into hasher: inout Hasher) { + let val = {% call swift::try(hash) %} {{ hash.return_type().unwrap()|lift_fn }}( + {% call swift::to_ffi_call(hash) %} + ) + hasher.combine(val) } + {%- else %} + {%- endmatch %} + {%- endfor %} - {%- endmatch -%} - {%- endif -%} - {% endfor %} } +{%- if obj.has_callback_interface() %} +{%- let callback_handler = format!("uniffiCallbackInterface{}", name) %} +{%- let callback_init = format!("uniffiCallbackInit{}", name) %} +{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %} +{%- let vtable_methods = obj.vtable_methods() %} +{%- let ffi_init_callback = obj.ffi_init_callback() %} +{% include "CallbackInterfaceImpl.swift" %} +{%- endif %} + public struct {{ ffi_converter_name }}: FfiConverter { + {%- if obj.has_callback_interface() %} + fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>() + {%- endif %} + typealias FfiType = UnsafeMutableRawPointer typealias SwiftType = {{ type_name }} + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { + return {{ impl_class_name }}(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + {%- if obj.has_callback_interface() %} + guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else { + fatalError("Cast to UnsafeMutableRawPointer failed") + } + return ptr + {%- else %} + return value.uniffiClonePointer() + {%- endif %} + } + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. @@ -115,15 +157,30 @@ public struct {{ ffi_converter_name }}: FfiConverter { // The Rust code won't compile if a pointer won't fit in a `UInt64`. writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) } +} - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return {{ type_name}}(unsafeFromRawPointer: pointer) +{# Objects as error #} +{%- if is_error %} +{# Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer holding a pointer #} +public struct {{ ffi_converter_name }}__as_error: FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> {{ type_name }} { + var reader = createReader(data: Data(rustBuffer: buf)) + return try {{ ffi_converter_name }}.read(from: &reader) } - public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { - return value.pointer + public static func lower(_ value: {{ type_name }}) -> RustBuffer { + fatalError("not implemented") + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { + fatalError("not implemented") + } + + public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { + fatalError("not implemented") } } +{%- endif %} {# We always write these public functions just in case the enum is used as diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift new file mode 100644 index 0000000000..7df953558a --- /dev/null +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift @@ -0,0 +1,12 @@ +{%- call swift::docstring_value(protocol_docstring, 0) %} +public protocol {{ protocol_name }} : AnyObject { + {% for meth in methods.iter() -%} + {%- call swift::docstring(meth, 4) %} + func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) -%}{% call swift::throws(meth) -%} + {%- match meth.return_type() -%} + {%- when Some with (return_type) %} -> {{ return_type|type_name -}} + {%- else -%} + {%- endmatch %} + {% endfor %} +} + diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift index 44de9dd358..c262a7a216 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift @@ -1,12 +1,14 @@ {%- let rec = ci|get_record_definition(name) %} +{%- call swift::docstring(rec, 0) %} public struct {{ type_name }} { {%- for field in rec.fields() %} - public var {{ field.name()|var_name }}: {{ field|type_name }} + {%- call swift::docstring(field, 4) %} + public {% if config.generate_immutable_records() %}let{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name }} {%- endfor %} // Default memberwise initializers are never public by default, so we // declare one manually. - public init({% call swift::field_list_decl(rec) %}) { + public init({% call swift::field_list_decl(rec, false) %}) { {%- for field in rec.fields() %} self.{{ field.name()|var_name }} = {{ field.name()|var_name }} {%- endfor %} @@ -14,6 +16,7 @@ public struct {{ type_name }} { } {% if !contains_object_references %} +{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %} extension {{ type_name }}: Equatable, Hashable { public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool { {%- for field in rec.fields() %} @@ -34,12 +37,16 @@ extension {{ type_name }}: Equatable, Hashable { public struct {{ ffi_converter_name }}: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { - return try {{ type_name }}( + return {%- if rec.has_fields() %} + try {{ type_name }}( {%- for field in rec.fields() %} - {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf) - {%- if !loop.last %}, {% endif %} + {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf) + {%- if !loop.last %}, {% endif %} {%- endfor %} ) + {%- else %} + {{ type_name }}() + {%- endif %} } public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift index 2f737b6635..a053334a30 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift @@ -7,6 +7,10 @@ fileprivate extension RustBuffer { self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) } + static func empty() -> RustBuffer { + RustBuffer(capacity: 0, len:0, data: nil) + } + static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer { try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) } } diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift index a2c6311931..ce946076f7 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift @@ -1,48 +1 @@ -{%- if func.is_async() %} - -public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} { - return {% call swift::try(func) %} await uniffiRustCallAsync( - rustFutureFunc: { - {{ func.ffi_func().name() }}( - {%- for arg in func.arguments() %} - {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} - {%- endfor %} - ) - }, - pollFunc: {{ func.ffi_rust_future_poll(ci) }}, - completeFunc: {{ func.ffi_rust_future_complete(ci) }}, - freeFunc: {{ func.ffi_rust_future_free(ci) }}, - {%- match func.return_type() %} - {%- when Some(return_type) %} - liftFunc: {{ return_type|lift_fn }}, - {%- when None %} - liftFunc: { $0 }, - {%- endmatch %} - {%- match func.throws_type() %} - {%- when Some with (e) %} - errorHandler: {{ e|ffi_converter_name }}.lift - {%- else %} - errorHandler: nil - {% endmatch %} - ) -} - -{% else %} - -{%- match func.return_type() -%} -{%- when Some with (return_type) %} - -public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} { - return {% call swift::try(func) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call(func) %} - ) -} - -{%- when None %} - -public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} { - {% call swift::to_ffi_call(func) %} -} - -{% endmatch %} -{%- endif %} +{%- call swift::func_decl("public func", func, 0) %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift index aba34f4b0b..5e26758f3c 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift @@ -64,9 +64,6 @@ {%- when Type::CallbackInterface { name, module_path } %} {%- include "CallbackInterfaceTemplate.swift" %} -{%- when Type::ForeignExecutor %} -{%- include "ForeignExecutorTemplate.swift" %} - {%- when Type::Custom { name, module_path, builtin } %} {%- include "CustomType.swift" %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift index 0a125e6f61..8692cd6ff0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift @@ -8,28 +8,97 @@ {%- call try(func) -%} {%- match func.throws_type() -%} {%- when Some with (e) -%} - rustCallWithError({{ e|ffi_converter_name }}.lift) { + rustCallWithError({{ e|ffi_error_converter_name }}.lift) { {%- else -%} rustCall() { {%- endmatch %} - {{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} $0) + {{ func.ffi_func().name() }}( + {%- if func.takes_self() %}self.uniffiClonePointer(),{% endif %} + {%- call arg_list_lowered(func) -%} $0 + ) } {%- endmacro -%} -{%- macro to_ffi_call_with_prefix(prefix, func) -%} -{% call try(func) %} - {%- match func.throws_type() %} - {%- when Some with (e) %} - rustCallWithError({{ e|ffi_converter_name }}.lift) { +// eg, `public func foo_bar() { body }` +{%- macro func_decl(func_decl, callable, indent) %} +{%- call docstring(callable, indent) %} +{{ func_decl }} {{ callable.name()|fn_name }}( + {%- call arg_list_decl(callable) -%}) + {%- call async(callable) %} + {%- call throws(callable) %} + {%- match callable.return_type() %} + {%- when Some with (return_type) %} -> {{ return_type|type_name }} + {%- when None %} + {%- endmatch %} { + {%- call call_body(callable) %} +} +{%- endmacro %} + +// primary ctor - no name, no return-type. +{%- macro ctor_decl(callable, indent) %} +{%- call docstring(callable, indent) %} +public convenience init( + {%- call arg_list_decl(callable) -%}) {%- call async(callable) %} {%- call throws(callable) %} { + {%- if callable.is_async() %} + let pointer = + {%- call call_async(callable) %} + {# The async mechanism returns an already constructed self. + We work around that by cloning the pointer from that object, then + assune the old object dies as there are no other references possible. + #} + .uniffiClonePointer() {%- else %} - rustCall() { - {% endmatch %} - {{ func.ffi_func().name() }}( - {{- prefix }}, {% call arg_list_lowered(func) -%} $0 - ) + let pointer = + {% call to_ffi_call(callable) %} + {%- endif %} + self.init(unsafeFromRawPointer: pointer) } {%- endmacro %} +{%- macro call_body(callable) %} +{%- if callable.is_async() %} + return {%- call call_async(callable) %} +{%- else %} +{%- match callable.return_type() -%} +{%- when Some with (return_type) %} + return {% call try(callable) %} {{ return_type|lift_fn }}({% call to_ffi_call(callable) %}) +{%- when None %} +{%- call to_ffi_call(callable) %} +{%- endmatch %} +{%- endif %} + +{%- endmacro %} + +{%- macro call_async(callable) %} + {% call try(callable) %} await uniffiRustCallAsync( + rustFutureFunc: { + {{ callable.ffi_func().name() }}( + {%- if callable.takes_self() %} + self.uniffiClonePointer(){% if !callable.arguments().is_empty() %},{% endif %} + {% endif %} + {%- for arg in callable.arguments() -%} + {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %} + {%- endfor %} + ) + }, + pollFunc: {{ callable.ffi_rust_future_poll(ci) }}, + completeFunc: {{ callable.ffi_rust_future_complete(ci) }}, + freeFunc: {{ callable.ffi_rust_future_free(ci) }}, + {%- match callable.return_type() %} + {%- when Some(return_type) %} + liftFunc: {{ return_type|lift_fn }}, + {%- when None %} + liftFunc: { $0 }, + {%- endmatch %} + {%- match callable.throws_type() %} + {%- when Some with (e) %} + errorHandler: {{ e|ffi_error_converter_name }}.lift + {%- else %} + errorHandler: nil + {% endmatch %} + ) +{%- endmacro %} + {%- macro arg_list_lowered(func) %} {%- for arg in func.arguments() %} {{ arg|lower_fn }}({{ arg.name()|var_name }}), @@ -56,17 +125,30 @@ // Field lists as used in Swift declarations of Records and Enums. // Note the var_name and type_name filters. -#} -{% macro field_list_decl(item) %} +{% macro field_list_decl(item, has_nameless_fields) %} {%- for field in item.fields() -%} + {%- call docstring(field, 8) %} + {%- if has_nameless_fields %} + {{- field|type_name -}} + {%- if !loop.last -%}, {%- endif -%} + {%- else -%} {{ field.name()|var_name }}: {{ field|type_name -}} {%- match field.default_value() %} {%- when Some with(literal) %} = {{ literal|literal_swift(field) }} {%- else %} {%- endmatch -%} {% if !loop.last %}, {% endif %} + {%- endif -%} {%- endfor %} {%- endmacro %} +{% macro field_name(field, field_num) %} +{%- if field.name().is_empty() -%} +v{{- field_num -}} +{%- else -%} +{{ field.name()|var_name }} +{%- endif -%} +{%- endmacro %} {% macro arg_list_protocol(func) %} {%- for arg in func.arguments() -%} @@ -75,15 +157,26 @@ {%- endfor %} {%- endmacro %} - {%- macro async(func) %} -{%- if func.is_async() %}async{% endif %} +{%- if func.is_async() %}async {% endif %} {%- endmacro -%} {%- macro throws(func) %} -{%- if func.throws() %}throws{% endif %} +{%- if func.throws() %}throws {% endif %} {%- endmacro -%} {%- macro try(func) %} {%- if func.throws() %}try {% else %}try! {% endif %} {%- endmacro -%} + +{%- macro docstring_value(maybe_docstring, indent_spaces) %} +{%- match maybe_docstring %} +{%- when Some(docstring) %} +{{ docstring|docstring(indent_spaces) }} +{%- else %} +{%- endmatch %} +{%- endmacro %} + +{%- macro docstring(defn, indent_spaces) %} +{%- call docstring_value(defn.docstring(), indent_spaces) %} +{%- endmacro %} diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift index c34d348efb..17fdde74e0 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift @@ -1,5 +1,10 @@ // This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! + +// swiftlint:disable all + +{%- call swift::docstring_value(ci.namespace_docstring(), 0) %} + {%- import "macros.swift" as swift %} import Foundation {%- for imported_class in self.imports() %} @@ -15,6 +20,7 @@ import {{ config.ffi_module_name() }} {% include "RustBufferTemplate.swift" %} {% include "Helpers.swift" %} +{% include "HandleMap.swift" %} // Public interface members begin here. {{ type_helper_code }} @@ -66,3 +72,5 @@ private func uniffiEnsureInitialized() { fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } } + +// swiftlint:enable all diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs index c3b2f15277..195a77696b 100644 --- a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs +++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs @@ -2,10 +2,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::{ - bindings::{RunScriptOptions, TargetLanguage}, - library_mode::generate_bindings, -}; +use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault}; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; @@ -15,6 +12,8 @@ use std::io::Write; use std::process::{Command, Stdio}; use uniffi_testing::UniFFITestHelper; +use crate::bindings::TargetLanguage; + /// Run Swift tests for a UniFFI test fixture pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> { run_script( @@ -36,7 +35,7 @@ pub fn run_script( args: Vec<String>, options: &RunScriptOptions, ) -> Result<()> { - let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?; + let script_path = Utf8Path::new(script_file).canonicalize_utf8()?; let test_helper = UniFFITestHelper::new(crate_name)?; let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?; let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?; @@ -126,8 +125,17 @@ struct GeneratedSources { impl GeneratedSources { fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result<Self> { - let sources = - generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?; + let sources = generate_bindings( + cdylib_path, + None, + &BindingGeneratorDefault { + target_languages: vec![TargetLanguage::Swift], + try_format_code: false, + }, + None, + out_dir, + false, + )?; let main_source = sources .iter() .find(|s| s.package.name == crate_name) @@ -169,7 +177,7 @@ fn create_command(program: &str, options: &RunScriptOptions) -> Command { if !options.show_compiler_messages { // This prevents most compiler messages, but not remarks command.arg("-suppress-warnings"); - // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument, + // This gets the remarks. Note: swift will eventually get a `-suppress-remarks` argument, // maybe we can eventually move to that command.stderr(Stdio::null()); } diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs index e3bca4f966..f176a7a684 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs @@ -33,9 +33,12 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` +use std::iter; + +use heck::ToUpperCamelCase; use uniffi_meta::Checksum; -use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::ffi::{FfiArgument, FfiCallbackFunction, FfiField, FfiFunction, FfiStruct, FfiType}; use super::object::Method; use super::{AsType, Type, TypeIterator}; @@ -52,18 +55,11 @@ pub struct CallbackInterface { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_init_callback: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option<String>, } impl CallbackInterface { - pub fn new(name: String, module_path: String) -> CallbackInterface { - CallbackInterface { - name, - module_path, - methods: Default::default(), - ffi_init_callback: Default::default(), - } - } - pub fn name(&self) -> &str { &self.name } @@ -77,18 +73,45 @@ impl CallbackInterface { } pub(super) fn derive_ffi_funcs(&mut self) { - self.ffi_init_callback.name = - uniffi_meta::init_callback_fn_symbol_name(&self.module_path, &self.name); - self.ffi_init_callback.arguments = vec![FfiArgument { - name: "callback_stub".to_string(), - type_: FfiType::ForeignCallback, - }]; - self.ffi_init_callback.return_type = None; + self.ffi_init_callback = + FfiFunction::callback_init(&self.module_path, &self.name, vtable_name(&self.name)); + } + + /// FfiCallbacks to define for our methods. + pub fn ffi_callbacks(&self) -> Vec<FfiCallbackFunction> { + ffi_callbacks(&self.name, &self.methods) + } + + /// The VTable FFI type + pub fn vtable(&self) -> FfiType { + FfiType::Struct(vtable_name(&self.name)) + } + + /// the VTable struct to define. + pub fn vtable_definition(&self) -> FfiStruct { + vtable_struct(&self.name, &self.methods) + } + + /// Vec of (ffi_callback, method) pairs + pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> { + self.methods + .iter() + .enumerate() + .map(|(i, method)| (method_ffi_callback(&self.name, method, i), method)) + .collect() } pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.methods.iter().flat_map(Method::iter_types)) } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + + pub fn has_async_method(&self) -> bool { + self.methods.iter().any(Method::is_async) + } } impl AsType for CallbackInterface { @@ -100,6 +123,139 @@ impl AsType for CallbackInterface { } } +impl TryFrom<uniffi_meta::CallbackInterfaceMetadata> for CallbackInterface { + type Error = anyhow::Error; + + fn try_from(meta: uniffi_meta::CallbackInterfaceMetadata) -> anyhow::Result<Self> { + Ok(Self { + name: meta.name, + module_path: meta.module_path, + methods: Default::default(), + ffi_init_callback: Default::default(), + docstring: meta.docstring.clone(), + }) + } +} + +/// [FfiCallbackFunction] functions for the methods of a callback/trait interface +pub fn ffi_callbacks(trait_name: &str, methods: &[Method]) -> Vec<FfiCallbackFunction> { + methods + .iter() + .enumerate() + .map(|(i, method)| method_ffi_callback(trait_name, method, i)) + .collect() +} + +pub fn method_ffi_callback(trait_name: &str, method: &Method, index: usize) -> FfiCallbackFunction { + if !method.is_async() { + FfiCallbackFunction { + name: method_ffi_callback_name(trait_name, index), + arguments: iter::once(FfiArgument::new("uniffi_handle", FfiType::UInt64)) + .chain(method.arguments().into_iter().map(Into::into)) + .chain(iter::once(match method.return_type() { + Some(t) => FfiArgument::new("uniffi_out_return", FfiType::from(t).reference()), + None => FfiArgument::new("uniffi_out_return", FfiType::VoidPointer), + })) + .collect(), + has_rust_call_status_arg: true, + return_type: None, + } + } else { + let completion_callback = + ffi_foreign_future_complete(method.return_type().map(FfiType::from)); + FfiCallbackFunction { + name: method_ffi_callback_name(trait_name, index), + arguments: iter::once(FfiArgument::new("uniffi_handle", FfiType::UInt64)) + .chain(method.arguments().into_iter().map(Into::into)) + .chain([ + FfiArgument::new( + "uniffi_future_callback", + FfiType::Callback(completion_callback.name), + ), + FfiArgument::new("uniffi_callback_data", FfiType::UInt64), + FfiArgument::new( + "uniffi_out_return", + FfiType::Struct("ForeignFuture".to_owned()).reference(), + ), + ]) + .collect(), + has_rust_call_status_arg: false, + return_type: None, + } + } +} + +/// Result struct to pass to the completion callback for async methods +pub fn foreign_future_ffi_result_struct(return_ffi_type: Option<FfiType>) -> FfiStruct { + let return_type_name = + FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case(); + FfiStruct { + name: format!("ForeignFutureStruct{return_type_name}"), + fields: match return_ffi_type { + Some(return_ffi_type) => vec![ + FfiField::new("return_value", return_ffi_type), + FfiField::new("call_status", FfiType::RustCallStatus), + ], + None => vec![ + // In Rust, `return_value` is `()` -- a ZST. + // ZSTs are not valid in `C`, but they also take up 0 space. + // Skip the `return_value` field to make the layout correct. + FfiField::new("call_status", FfiType::RustCallStatus), + ], + }, + } +} + +/// Definition for callback functions to complete an async callback interface method +pub fn ffi_foreign_future_complete(return_ffi_type: Option<FfiType>) -> FfiCallbackFunction { + let return_type_name = + FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case(); + FfiCallbackFunction { + name: format!("ForeignFutureComplete{return_type_name}"), + arguments: vec![ + FfiArgument::new("callback_data", FfiType::UInt64), + FfiArgument::new( + "result", + FfiType::Struct(format!("ForeignFutureStruct{return_type_name}")), + ), + ], + return_type: None, + has_rust_call_status_arg: false, + } +} + +/// [FfiStruct] for a callback/trait interface VTable +/// +/// This struct has a FfiCallbackFunction field for each method, plus extra fields for special +/// methods +pub fn vtable_struct(trait_name: &str, methods: &[Method]) -> FfiStruct { + FfiStruct { + name: vtable_name(trait_name), + fields: methods + .iter() + .enumerate() + .map(|(i, method)| { + FfiField::new( + method.name(), + FfiType::Callback(format!("CallbackInterface{trait_name}Method{i}")), + ) + }) + .chain([FfiField::new( + "uniffi_free", + FfiType::Callback("CallbackInterfaceFree".to_owned()), + )]) + .collect(), + } +} + +pub fn method_ffi_callback_name(trait_name: &str, index: usize) -> String { + format!("CallbackInterface{trait_name}Method{index}") +} + +pub fn vtable_name(trait_name: &str) -> String { + format!("VTableCallbackInterface{trait_name}") +} + #[cfg(test)] mod test { use super::super::ComponentInterface; @@ -146,4 +302,21 @@ mod test { assert_eq!(callbacks_two.methods()[0].name(), "two"); assert_eq!(callbacks_two.methods()[1].name(), "too"); } + + #[test] + fn test_docstring_callback_interface() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + callback interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_callback_interface_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs index 82baf1dd50..a666cc3605 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs @@ -94,7 +94,9 @@ //! //! ``` //! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" -//! # namespace example {}; +//! # namespace example { +//! # [Throws=Example] void func(); +//! # }; //! # [Error] //! # enum Example { //! # "one", @@ -130,7 +132,9 @@ //! //! ``` //! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##" -//! # namespace example {}; +//! # namespace example { +//! # [Throws=Example] void func(); +//! # }; //! # [Error] //! # interface Example { //! # one(); @@ -159,7 +163,7 @@ use anyhow::Result; use uniffi_meta::Checksum; use super::record::Field; -use super::{AsType, Type, TypeIterator}; +use super::{AsType, Literal, Type, TypeIterator}; /// Represents an enum with named variants, each of which may have named /// and typed fields. @@ -170,6 +174,7 @@ use super::{AsType, Type, TypeIterator}; pub struct Enum { pub(super) name: String, pub(super) module_path: String, + pub(super) discr_type: Option<Type>, pub(super) variants: Vec<Variant>, // NOTE: `flat` is a misleading name and to make matters worse, has 2 different // meanings depending on the context :( @@ -189,6 +194,9 @@ pub struct Enum { // * For an Enum not used as an error but which has no variants with data, `flat` will be // false when generating the scaffolding but `true` when generating bindings. pub(super) flat: bool, + pub(super) non_exhaustive: bool, + #[checksum_ignore] + pub(super) docstring: Option<String>, } impl Enum { @@ -200,14 +208,61 @@ impl Enum { &self.variants } + // Get the literal value to use for the specified variant's discriminant. + // Follows Rust's rules when mixing specified and unspecified values; please + // file a bug if you find a case where it does not. + // However, it *does not* attempt to handle error cases - either cases where + // a discriminant is not unique, or where a discriminant would overflow the + // repr. The intention is that the Rust compiler itself will fail to build + // in those cases, so by the time this get's run we can be confident these + // error cases can't exist. + pub fn variant_discr(&self, variant_index: usize) -> Result<Literal> { + if variant_index >= self.variants.len() { + anyhow::bail!("Invalid variant index {variant_index}"); + } + let mut next = 0; + let mut this; + let mut this_lit = Literal::new_uint(0); + for v in self.variants().iter().take(variant_index + 1) { + (this, this_lit) = match v.discr { + None => ( + next, + if (next as i64) < 0 { + Literal::new_int(next as i64) + } else { + Literal::new_uint(next) + }, + ), + Some(Literal::UInt(v, _, _)) => (v, Literal::new_uint(v)), + // in-practice, Literal::Int == a negative number. + Some(Literal::Int(v, _, _)) => (v as u64, Literal::new_int(v)), + _ => anyhow::bail!("Invalid literal type {v:?}"), + }; + next = this.wrapping_add(1); + } + Ok(this_lit) + } + + pub fn variant_discr_type(&self) -> &Option<Type> { + &self.discr_type + } + pub fn is_flat(&self) -> bool { self.flat } + pub fn is_non_exhaustive(&self) -> bool { + self.non_exhaustive + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.variants.iter().flat_map(Variant::iter_types)) } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + // Sadly can't use TryFrom due to the 'is_flat' complication. pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result<Self> { // This is messy - error enums are considered "flat" if the user @@ -218,12 +273,15 @@ impl Enum { Ok(Self { name: meta.name, module_path: meta.module_path, + discr_type: meta.discr_type, variants: meta .variants .into_iter() .map(TryInto::try_into) .collect::<Result<_>>()?, flat, + non_exhaustive: meta.non_exhaustive, + docstring: meta.docstring.clone(), }) } } @@ -243,7 +301,10 @@ impl AsType for Enum { #[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)] pub struct Variant { pub(super) name: String, + pub(super) discr: Option<Literal>, pub(super) fields: Vec<Field>, + #[checksum_ignore] + pub(super) docstring: Option<String>, } impl Variant { @@ -259,6 +320,14 @@ impl Variant { !self.fields.is_empty() } + pub fn has_nameless_fields(&self) -> bool { + self.fields.iter().any(|f| f.name.is_empty()) + } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.fields.iter().flat_map(Field::iter_types)) } @@ -270,11 +339,13 @@ impl TryFrom<uniffi_meta::VariantMetadata> for Variant { fn try_from(meta: uniffi_meta::VariantMetadata) -> Result<Self> { Ok(Self { name: meta.name, + discr: meta.discr, fields: meta .fields .into_iter() .map(TryInto::try_into) .collect::<Result<_>>()?, + docstring: meta.docstring.clone(), }) } } @@ -447,7 +518,10 @@ mod test { #[test] fn test_variants() { const UDL: &str = r#" - namespace test{}; + namespace test{ + [Throws=Testing] + void func(); + }; [Error] enum Testing { "one", "two", "three" }; "#; @@ -486,7 +560,10 @@ mod test { #[test] fn test_variant_data() { const UDL: &str = r#" - namespace test{}; + namespace test{ + [Throws=Testing] + void func(); + }; [Error] interface Testing { @@ -564,4 +641,141 @@ mod test { vec!["Normal", "Error"] ); } + + fn variant(val: Option<u64>) -> Variant { + Variant { + name: "v".to_string(), + discr: val.map(Literal::new_uint), + fields: vec![], + docstring: None, + } + } + + fn check_discrs(e: &mut Enum, vs: Vec<Variant>) -> Vec<u64> { + e.variants = vs; + (0..e.variants.len()) + .map(|i| e.variant_discr(i).unwrap()) + .map(|l| match l { + Literal::UInt(v, _, _) => v, + _ => unreachable!(), + }) + .collect() + } + + #[test] + fn test_variant_values() { + let mut e = Enum { + module_path: "test".to_string(), + name: "test".to_string(), + discr_type: None, + variants: vec![], + flat: false, + non_exhaustive: false, + docstring: None, + }; + + assert!(e.variant_discr(0).is_err()); + + // single values + assert_eq!(check_discrs(&mut e, vec![variant(None)]), vec![0]); + assert_eq!(check_discrs(&mut e, vec![variant(Some(3))]), vec![3]); + + // no values + assert_eq!( + check_discrs(&mut e, vec![variant(None), variant(None)]), + vec![0, 1] + ); + + // values + assert_eq!( + check_discrs(&mut e, vec![variant(Some(1)), variant(Some(3))]), + vec![1, 3] + ); + + // mixed values + assert_eq!( + check_discrs(&mut e, vec![variant(None), variant(Some(3)), variant(None)]), + vec![0, 3, 4] + ); + + assert_eq!( + check_discrs( + &mut e, + vec![variant(Some(4)), variant(None), variant(Some(1))] + ), + vec![4, 5, 1] + ); + } + + #[test] + fn test_docstring_enum() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + enum Testing { "foo" }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_enum_variant() { + const UDL: &str = r#" + namespace test{}; + enum Testing { + /// informative docstring + "foo" + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_associated_enum() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + [Enum] + interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_associated_enum_variant() { + const UDL: &str = r#" + namespace test{}; + [Enum] + interface Testing { + /// informative docstring + testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_enum_definition("Testing").unwrap().variants()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs index d18aaf8262..b27cb78477 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs @@ -47,20 +47,49 @@ pub enum FfiType { /// A borrowed reference to some raw bytes owned by foreign language code. /// The provider of this reference must keep it alive for the duration of the receiving call. ForeignBytes, - /// Pointer to a callback function that handles all callbacks on the foreign language side. - ForeignCallback, - /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can - /// either use an actual pointer or a usized integer. - ForeignExecutorHandle, - /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor - ForeignExecutorCallback, - /// Pointer to a Rust future - RustFutureHandle, - /// Continuation function for a Rust future - RustFutureContinuationCallback, - RustFutureContinuationData, - // TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something. - // We don't need that yet and it's possible we never will, so it isn't here for now. + /// Pointer to a callback function. The inner value which matches one of the callback + /// definitions in [crate::ComponentInterface::ffi_definitions]. + Callback(String), + /// Pointer to a FFI struct (e.g. a VTable). The inner value matches one of the struct + /// definitions in [crate::ComponentInterface::ffi_definitions]. + Struct(String), + /// Opaque 64-bit handle + /// + /// These are used to pass objects across the FFI. + Handle, + RustCallStatus, + /// Pointer to an FfiType. + Reference(Box<FfiType>), + /// Opaque pointer + VoidPointer, +} + +impl FfiType { + pub fn reference(self) -> FfiType { + FfiType::Reference(Box::new(self)) + } + + /// Unique name for an FFI return type + pub fn return_type_name(return_type: Option<&FfiType>) -> String { + match return_type { + Some(t) => match t { + FfiType::UInt8 => "u8".to_owned(), + FfiType::Int8 => "i8".to_owned(), + FfiType::UInt16 => "u16".to_owned(), + FfiType::Int16 => "i16".to_owned(), + FfiType::UInt32 => "u32".to_owned(), + FfiType::Int32 => "i32".to_owned(), + FfiType::UInt64 => "u64".to_owned(), + FfiType::Int64 => "i64".to_owned(), + FfiType::Float32 => "f32".to_owned(), + FfiType::Float64 => "f64".to_owned(), + FfiType::RustArcPtr(_) => "pointer".to_owned(), + FfiType::RustBuffer(_) => "rust_buffer".to_owned(), + _ => unimplemented!("FFI return type: {t:?}"), + }, + None => "void".to_owned(), + } + } } /// When passing data across the FFI, each `Type` value will be lowered into a corresponding @@ -94,7 +123,6 @@ impl From<&Type> for FfiType { Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()), // Callback interfaces are passed as opaque integer handles. Type::CallbackInterface { .. } => FfiType::UInt64, - Type::ForeignExecutor => FfiType::ForeignExecutorHandle, // Other types are serialized into a bytebuffer and deserialized on the other side. Type::Enum { .. } | Type::Record { .. } @@ -107,6 +135,11 @@ impl From<&Type> for FfiType { name, kind: ExternalKind::Interface, .. + } + | Type::External { + name, + kind: ExternalKind::Trait, + .. } => FfiType::RustArcPtr(name.clone()), Type::External { name, @@ -131,6 +164,24 @@ impl From<&&Type> for FfiType { } } +/// An Ffi definition +#[derive(Debug, Clone)] +pub enum FfiDefinition { + Function(FfiFunction), + CallbackFunction(FfiCallbackFunction), + Struct(FfiStruct), +} + +impl FfiDefinition { + pub fn name(&self) -> &str { + match self { + Self::Function(f) => f.name(), + Self::CallbackFunction(f) => f.name(), + Self::Struct(s) => s.name(), + } + } +} + /// Represents an "extern C"-style function that will be part of the FFI. /// /// These can't be declared explicitly in the UDL, but rather, are derived automatically @@ -150,6 +201,19 @@ pub struct FfiFunction { } impl FfiFunction { + pub fn callback_init(module_path: &str, trait_name: &str, vtable_name: String) -> Self { + Self { + name: uniffi_meta::init_callback_vtable_fn_symbol_name(module_path, trait_name), + arguments: vec![FfiArgument { + name: "vtable".to_string(), + type_: FfiType::Struct(vtable_name).reference(), + }], + return_type: None, + has_rust_call_status_arg: false, + ..Self::default() + } + } + pub fn name(&self) -> &str { &self.name } @@ -181,7 +245,7 @@ impl FfiFunction { ) { self.arguments = args.into_iter().collect(); if self.is_async() { - self.return_type = Some(FfiType::RustFutureHandle); + self.return_type = Some(FfiType::Handle); self.has_rust_call_status_arg = false; } else { self.return_type = return_type; @@ -212,14 +276,113 @@ pub struct FfiArgument { } impl FfiArgument { + pub fn new(name: impl Into<String>, type_: FfiType) -> Self { + Self { + name: name.into(), + type_, + } + } + pub fn name(&self) -> &str { &self.name } + pub fn type_(&self) -> FfiType { self.type_.clone() } } +/// Represents an "extern C"-style callback function +/// +/// These are defined in the foreign code and passed to Rust as a function pointer. +#[derive(Debug, Default, Clone)] +pub struct FfiCallbackFunction { + // Name for this function type. This matches the value inside `FfiType::Callback` + pub(super) name: String, + pub(super) arguments: Vec<FfiArgument>, + pub(super) return_type: Option<FfiType>, + pub(super) has_rust_call_status_arg: bool, +} + +impl FfiCallbackFunction { + pub fn name(&self) -> &str { + &self.name + } + + pub fn arguments(&self) -> Vec<&FfiArgument> { + self.arguments.iter().collect() + } + + pub fn return_type(&self) -> Option<&FfiType> { + self.return_type.as_ref() + } + + pub fn has_rust_call_status_arg(&self) -> bool { + self.has_rust_call_status_arg + } +} + +/// Represents a repr(C) struct used in the FFI +#[derive(Debug, Default, Clone)] +pub struct FfiStruct { + pub(super) name: String, + pub(super) fields: Vec<FfiField>, +} + +impl FfiStruct { + /// Get the name of this struct + pub fn name(&self) -> &str { + &self.name + } + + /// Get the fields for this struct + pub fn fields(&self) -> &[FfiField] { + &self.fields + } +} + +/// Represents a field of an [FfiStruct] +#[derive(Debug, Clone)] +pub struct FfiField { + pub(super) name: String, + pub(super) type_: FfiType, +} + +impl FfiField { + pub fn new(name: impl Into<String>, type_: FfiType) -> Self { + Self { + name: name.into(), + type_, + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn type_(&self) -> FfiType { + self.type_.clone() + } +} + +impl From<FfiFunction> for FfiDefinition { + fn from(value: FfiFunction) -> FfiDefinition { + FfiDefinition::Function(value) + } +} + +impl From<FfiStruct> for FfiDefinition { + fn from(value: FfiStruct) -> FfiDefinition { + FfiDefinition::Struct(value) + } +} + +impl From<FfiCallbackFunction> for FfiDefinition { + fn from(value: FfiCallbackFunction) -> FfiDefinition { + FfiDefinition::CallbackFunction(value) + } +} + #[cfg(test)] mod test { // There's not really much to test here to be honest, diff --git a/third_party/rust/uniffi_bindgen/src/interface/function.rs b/third_party/rust/uniffi_bindgen/src/interface/function.rs index 2d18288c1c..8effc4c876 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/function.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs @@ -59,6 +59,8 @@ pub struct Function { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option<String>, pub(super) throws: Option<Type>, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. @@ -128,6 +130,10 @@ impl Function { .chain(self.return_type.iter().flat_map(Type::iter_types)), ) } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } } impl From<uniffi_meta::FnParamMetadata> for Argument { @@ -163,6 +169,7 @@ impl From<uniffi_meta::FnMetadata> for Function { arguments, return_type, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws, checksum_fn_name, checksum: meta.checksum, @@ -242,6 +249,9 @@ pub trait Callable { fn return_type(&self) -> Option<Type>; fn throws_type(&self) -> Option<Type>; fn is_async(&self) -> bool; + fn takes_self(&self) -> bool { + false + } fn result_type(&self) -> ResultType { ResultType { return_type: self.return_type(), @@ -311,6 +321,10 @@ impl<T: Callable> Callable for &T { fn is_async(&self) -> bool { (*self).is_async() } + + fn takes_self(&self) -> bool { + (*self).takes_self() + } } #[cfg(test)] @@ -364,4 +378,22 @@ mod test { ); Ok(()) } + + #[test] + fn test_docstring_function() { + const UDL: &str = r#" + namespace test { + /// informative docstring + void testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_function_definition("testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/mod.rs index 8e4df2149b..90a941637a 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs @@ -67,7 +67,9 @@ mod record; pub use record::{Field, Record}; pub mod ffi; -pub use ffi::{FfiArgument, FfiFunction, FfiType}; +pub use ffi::{ + FfiArgument, FfiCallbackFunction, FfiDefinition, FfiField, FfiFunction, FfiStruct, FfiType, +}; pub use uniffi_meta::Radix; use uniffi_meta::{ ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata, @@ -139,6 +141,11 @@ impl ComponentInterface { self.types.namespace ); } + + if group.namespace_docstring.is_some() { + self.types.namespace_docstring = group.namespace_docstring.clone(); + } + // Unconditionally add the String type, which is used by the panic handling self.types.add_known_type(&uniffi_meta::Type::String)?; crate::macro_metadata::add_group_to_ci(self, group)?; @@ -153,6 +160,10 @@ impl ComponentInterface { &self.types.namespace.name } + pub fn namespace_docstring(&self) -> Option<&str> { + self.types.namespace_docstring.as_deref() + } + pub fn uniffi_contract_version(&self) -> u32 { // This is set by the scripts in the version-mismatch fixture let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION"); @@ -204,6 +215,29 @@ impl ComponentInterface { self.objects.iter().find(|o| o.name == name) } + fn callback_interface_callback_definitions( + &self, + ) -> impl IntoIterator<Item = FfiCallbackFunction> + '_ { + self.callback_interfaces + .iter() + .flat_map(|cbi| cbi.ffi_callbacks()) + .chain(self.objects.iter().flat_map(|o| o.ffi_callbacks())) + } + + /// Get the definitions for callback FFI functions + /// + /// These are defined by the foreign code and invoked by Rust. + fn callback_interface_vtable_definitions(&self) -> impl IntoIterator<Item = FfiStruct> + '_ { + self.callback_interface_definitions() + .iter() + .map(|cbi| cbi.vtable_definition()) + .chain( + self.object_definitions() + .iter() + .flat_map(|o| o.vtable_definition()), + ) + } + /// Get the definitions for every Callback Interface type in the interface. pub fn callback_interface_definitions(&self) -> &[CallbackInterface] { &self.callback_interfaces @@ -215,6 +249,17 @@ impl ComponentInterface { self.callback_interfaces.iter().find(|o| o.name == name) } + /// Get the definitions for every Callback Interface type in the interface. + pub fn has_async_callback_interface_definition(&self) -> bool { + self.callback_interfaces + .iter() + .any(|cbi| cbi.has_async_method()) + || self + .objects + .iter() + .any(|o| o.has_callback_interface() && o.has_async_method()) + } + /// Get the definitions for every Method type in the interface. pub fn iter_callables(&self) -> impl Iterator<Item = &dyn Callable> { // Each of the `as &dyn Callable` casts is a trivial cast, but it seems like the clearest @@ -241,13 +286,19 @@ impl ComponentInterface { let fielded = !e.is_flat(); // For flat errors, we should only generate read() methods if we need them to support // callback interface errors - let used_in_callback_interface = self + let used_in_foreign_interface = self .callback_interface_definitions() .iter() .flat_map(|cb| cb.methods()) + .chain( + self.object_definitions() + .iter() + .filter(|o| o.has_callback_interface()) + .flat_map(|o| o.methods()), + ) .any(|m| m.throws_type() == Some(&e.as_type())); - self.is_name_used_as_error(&e.name) && (fielded || used_in_callback_interface) + self.is_name_used_as_error(&e.name) && (fielded || used_in_foreign_interface) } /// Get details about all `Type::External` types. @@ -304,8 +355,17 @@ impl ComponentInterface { /// This is important to know in language bindings that cannot integrate object types /// tightly with the host GC, and hence need to perform manual destruction of objects. pub fn item_contains_object_references(&self, item: &Type) -> bool { - self.iter_types_in_item(item) - .any(|t| matches!(t, Type::Object { .. })) + // this is surely broken for external records with object refs? + self.iter_types_in_item(item).any(|t| { + matches!( + t, + Type::Object { .. } + | Type::External { + kind: ExternalKind::Interface, + .. + } + ) + }) } /// Check whether the given item contains any (possibly nested) unsigned types @@ -335,6 +395,13 @@ impl ComponentInterface { .any(|t| matches!(t, Type::Map { .. })) } + /// Check whether the interface contains any object types + pub fn contains_object_types(&self) -> bool { + self.types + .iter_known_types() + .any(|t| matches!(t, Type::Object { .. })) + } + // The namespace to use in crate-level FFI function definitions. Not used as the ffi // namespace for types - each type has its own `module_path` which is used for them. fn ffi_namespace(&self) -> &str { @@ -364,7 +431,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "size".to_string(), - type_: FfiType::Int32, + type_: FfiType::UInt64, }], return_type: Some(FfiType::RustBuffer(None)), has_rust_call_status_arg: true, @@ -420,7 +487,7 @@ impl ComponentInterface { }, FfiArgument { name: "additional".to_string(), - type_: FfiType::Int32, + type_: FfiType::UInt64, }, ], return_type: Some(FfiType::RustBuffer(None)), @@ -429,24 +496,6 @@ impl ComponentInterface { } } - /// Builtin FFI function to set the Rust Future continuation callback - pub fn ffi_rust_future_continuation_callback_set(&self) -> FfiFunction { - FfiFunction { - name: format!( - "ffi_{}_rust_future_continuation_callback_set", - self.ffi_namespace() - ), - arguments: vec![FfiArgument { - name: "callback".to_owned(), - type_: FfiType::RustFutureContinuationCallback, - }], - return_type: None, - is_async: false, - has_rust_call_status_arg: false, - is_object_free_function: false, - } - } - /// Builtin FFI function to poll a Rust future. pub fn ffi_rust_future_poll(&self, return_ffi_type: Option<FfiType>) -> FfiFunction { FfiFunction { @@ -455,12 +504,15 @@ impl ComponentInterface { arguments: vec![ FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, + }, + FfiArgument { + name: "callback".to_owned(), + type_: FfiType::Callback("RustFutureContinuationCallback".to_owned()), }, - // Data to pass to the continuation FfiArgument { - name: "uniffi_callback".to_owned(), - type_: FfiType::RustFutureContinuationData, + name: "callback_data".to_owned(), + type_: FfiType::Handle, }, ], return_type: None, @@ -478,7 +530,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: return_ffi_type, has_rust_call_status_arg: true, @@ -493,7 +545,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -508,7 +560,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -518,29 +570,17 @@ impl ComponentInterface { fn rust_future_ffi_fn_name(&self, base_name: &str, return_ffi_type: Option<FfiType>) -> String { let namespace = self.ffi_namespace(); - match return_ffi_type { - Some(t) => match t { - FfiType::UInt8 => format!("ffi_{namespace}_{base_name}_u8"), - FfiType::Int8 => format!("ffi_{namespace}_{base_name}_i8"), - FfiType::UInt16 => format!("ffi_{namespace}_{base_name}_u16"), - FfiType::Int16 => format!("ffi_{namespace}_{base_name}_i16"), - FfiType::UInt32 => format!("ffi_{namespace}_{base_name}_u32"), - FfiType::Int32 => format!("ffi_{namespace}_{base_name}_i32"), - FfiType::UInt64 => format!("ffi_{namespace}_{base_name}_u64"), - FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"), - FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"), - FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"), - FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"), - FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"), - _ => unimplemented!("FFI return type: {t:?}"), - }, - None => format!("ffi_{namespace}_{base_name}_void"), - } + let return_type_name = FfiType::return_type_name(return_ffi_type.as_ref()); + format!("ffi_{namespace}_{base_name}_{return_type_name}") } /// Does this interface contain async functions? pub fn has_async_fns(&self) -> bool { self.iter_ffi_function_definitions().any(|f| f.is_async()) + || self + .callback_interfaces + .iter() + .any(CallbackInterface::has_async_method) } /// Iterate over `T` parameters of the `FutureCallback<T>` callbacks in this interface @@ -561,6 +601,73 @@ impl ComponentInterface { unique_results.into_iter() } + /// Iterate over all Ffi definitions + pub fn ffi_definitions(&self) -> impl Iterator<Item = FfiDefinition> + '_ { + // Note: for languages like Python it's important to keep things in dependency order. + // For example some FFI function definitions depend on FFI struct definitions, so the + // function definitions come last. + self.builtin_ffi_definitions() + .into_iter() + .chain( + self.callback_interface_callback_definitions() + .into_iter() + .map(Into::into), + ) + .chain( + self.callback_interface_vtable_definitions() + .into_iter() + .map(Into::into), + ) + .chain(self.iter_ffi_function_definitions().map(Into::into)) + } + + fn builtin_ffi_definitions(&self) -> impl IntoIterator<Item = FfiDefinition> + '_ { + [ + FfiCallbackFunction { + name: "RustFutureContinuationCallback".to_owned(), + arguments: vec![ + FfiArgument::new("data", FfiType::UInt64), + FfiArgument::new("poll_result", FfiType::Int8), + ], + return_type: None, + has_rust_call_status_arg: false, + } + .into(), + FfiCallbackFunction { + name: "ForeignFutureFree".to_owned(), + arguments: vec![FfiArgument::new("handle", FfiType::UInt64)], + return_type: None, + has_rust_call_status_arg: false, + } + .into(), + FfiCallbackFunction { + name: "CallbackInterfaceFree".to_owned(), + arguments: vec![FfiArgument::new("handle", FfiType::UInt64)], + return_type: None, + has_rust_call_status_arg: false, + } + .into(), + FfiStruct { + name: "ForeignFuture".to_owned(), + fields: vec![ + FfiField::new("handle", FfiType::UInt64), + FfiField::new("free", FfiType::Callback("ForeignFutureFree".to_owned())), + ], + } + .into(), + ] + .into_iter() + .chain( + self.all_possible_return_ffi_types() + .flat_map(|return_type| { + [ + callbacks::foreign_future_ffi_result_struct(return_type.clone()).into(), + callbacks::ffi_foreign_future_complete(return_type).into(), + ] + }), + ) + } + /// List the definitions of all FFI functions in the interface. /// /// The set of FFI functions is derived automatically from the set of higher-level types @@ -569,9 +676,8 @@ impl ComponentInterface { self.iter_user_ffi_function_definitions() .cloned() .chain(self.iter_rust_buffer_ffi_function_definitions()) - .chain(self.iter_futures_ffi_function_definitons()) + .chain(self.iter_futures_ffi_function_definitions()) .chain(self.iter_checksum_ffi_functions()) - .chain(self.ffi_foreign_executor_callback_set()) .chain([self.ffi_uniffi_contract_version()]) } @@ -618,9 +724,8 @@ impl ComponentInterface { .into_iter() } - /// List all FFI functions definitions for async functionality. - pub fn iter_futures_ffi_function_definitons(&self) -> impl Iterator<Item = FfiFunction> + '_ { - let all_possible_return_ffi_types = [ + fn all_possible_return_ffi_types(&self) -> impl Iterator<Item = Option<FfiType>> { + [ Some(FfiType::UInt8), Some(FfiType::Int8), Some(FfiType::UInt16), @@ -631,46 +736,26 @@ impl ComponentInterface { Some(FfiType::Int64), Some(FfiType::Float32), Some(FfiType::Float64), - // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future - // complete scaffolding function, so we just use a placeholder value here. + // RustBuffer and RustArcPtr have an inner field which we have to fill in with a + // placeholder value. Some(FfiType::RustArcPtr("".to_owned())), Some(FfiType::RustBuffer(None)), None, - ]; - - iter::once(self.ffi_rust_future_continuation_callback_set()).chain( - all_possible_return_ffi_types - .into_iter() - .flat_map(|return_type| { - [ - self.ffi_rust_future_poll(return_type.clone()), - self.ffi_rust_future_cancel(return_type.clone()), - self.ffi_rust_future_free(return_type.clone()), - self.ffi_rust_future_complete(return_type), - ] - }), - ) + ] + .into_iter() } - /// The ffi_foreign_executor_callback_set FFI function - /// - /// We only include this in the FFI if the `ForeignExecutor` type is actually used - pub fn ffi_foreign_executor_callback_set(&self) -> Option<FfiFunction> { - if self.types.contains(&Type::ForeignExecutor) { - Some(FfiFunction { - name: format!("ffi_{}_foreign_executor_callback_set", self.ffi_namespace()), - arguments: vec![FfiArgument { - name: "callback".into(), - type_: FfiType::ForeignExecutorCallback, - }], - return_type: None, - is_async: false, - has_rust_call_status_arg: false, - is_object_free_function: false, + /// List all FFI functions definitions for async functionality. + pub fn iter_futures_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ { + self.all_possible_return_ffi_types() + .flat_map(|return_type| { + [ + self.ffi_rust_future_poll(return_type.clone()), + self.ffi_rust_future_cancel(return_type.clone()), + self.ffi_rust_future_free(return_type.clone()), + self.ffi_rust_future_complete(return_type), + ] }) - } else { - None - } } /// List all API checksums to check @@ -778,6 +863,8 @@ impl ComponentInterface { bail!("Conflicting type definition for \"{}\"", defn.name()); } self.types.add_known_types(defn.iter_types())?; + defn.throws_name() + .map(|n| self.errors.insert(n.to_string())); self.functions.push(defn); Ok(()) @@ -789,6 +876,8 @@ impl ComponentInterface { let defn: Constructor = meta.into(); self.types.add_known_types(defn.iter_types())?; + defn.throws_name() + .map(|n| self.errors.insert(n.to_string())); object.constructors.push(defn); Ok(()) @@ -800,6 +889,9 @@ impl ComponentInterface { .ok_or_else(|| anyhow!("add_method_meta: object {} not found", &method.object_name))?; self.types.add_known_types(method.iter_types())?; + method + .throws_name() + .map(|n| self.errors.insert(n.to_string())); method.object_impl = object.imp; object.methods.push(method); Ok(()) @@ -825,10 +917,6 @@ impl ComponentInterface { Ok(()) } - pub(super) fn note_name_used_as_error(&mut self, name: &str) { - self.errors.insert(name.to_string()); - } - pub fn is_name_used_as_error(&self, name: &str) -> bool { self.errors.contains(name) } @@ -856,6 +944,9 @@ impl ComponentInterface { self.callback_interface_throws_types.insert(error.clone()); } self.types.add_known_types(method.iter_types())?; + method + .throws_name() + .map(|n| self.errors.insert(n.to_string())); cbi.methods.push(method); } else { self.add_method_meta(meta)?; @@ -880,31 +971,6 @@ impl ComponentInterface { bail!("Conflicting type definition for \"{}\"", f.name()); } } - - for ty in self.iter_types() { - match ty { - Type::Object { name, .. } => { - ensure!( - self.objects.iter().any(|o| o.name == *name), - "Object `{name}` has no definition" - ); - } - Type::Record { name, .. } => { - ensure!( - self.records.contains_key(name), - "Record `{name}` has no definition", - ); - } - Type::Enum { name, .. } => { - ensure!( - self.enums.contains_key(name), - "Enum `{name}` has no definition", - ); - } - _ => {} - } - } - Ok(()) } @@ -1047,7 +1113,7 @@ fn throws_name(throws: &Option<Type>) -> Option<&str> { // Type has no `name()` method, just `canonical_name()` which isn't what we want. match throws { None => None, - Some(Type::Enum { name, .. }) => Some(name), + Some(Type::Enum { name, .. }) | Some(Type::Object { name, .. }) => Some(name), _ => panic!("unknown throw type: {throws:?}"), } } @@ -1089,35 +1155,50 @@ mod test { let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err(); assert_eq!( err.to_string(), - "Mismatching definition for enum `Testing`!\nexisting definition: Enum { + "Mismatching definition for enum `Testing`! +existing definition: Enum { name: \"Testing\", module_path: \"crate_name\", + discr_type: None, variants: [ Variant { name: \"one\", + discr: None, fields: [], + docstring: None, }, Variant { name: \"two\", + discr: None, fields: [], + docstring: None, }, ], flat: true, + non_exhaustive: false, + docstring: None, }, new definition: Enum { name: \"Testing\", module_path: \"crate_name\", + discr_type: None, variants: [ Variant { name: \"three\", + discr: None, fields: [], + docstring: None, }, Variant { name: \"four\", + discr: None, fields: [], + docstring: None, }, ], flat: true, + non_exhaustive: false, + docstring: None, }", ); @@ -1231,4 +1312,25 @@ new definition: Enum { imp: ObjectImpl::Struct, })); } + + #[test] + fn test_docstring_namespace() { + const UDL: &str = r#" + /// informative docstring + namespace test{}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.namespace_docstring().unwrap(), "informative docstring"); + } + + #[test] + fn test_multiline_docstring() { + const UDL: &str = r#" + /// informative + /// docstring + namespace test{}; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!(ci.namespace_docstring().unwrap(), "informative\ndocstring"); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/object.rs b/third_party/rust/uniffi_bindgen/src/interface/object.rs index 942032b3c6..2b86e54a45 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/object.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs @@ -57,12 +57,11 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` -use std::iter; - use anyhow::Result; use uniffi_meta::Checksum; -use super::ffi::{FfiArgument, FfiFunction, FfiType}; +use super::callbacks; +use super::ffi::{FfiArgument, FfiCallbackFunction, FfiFunction, FfiStruct, FfiType}; use super::function::{Argument, Callable}; use super::{AsType, ObjectImpl, Type, TypeIterator}; @@ -92,14 +91,24 @@ pub struct Object { // a regular method (albeit with a generated name) // XXX - this should really be a HashSet, but not enough transient types support hash to make it worthwhile now. pub(super) uniffi_traits: Vec<UniffiTrait>, - // We don't include the FfiFunc in the hash calculation, because: + // We don't include the FfiFuncs in the hash calculation, because: // - it is entirely determined by the other fields, // so excluding it is safe. // - its `name` property includes a checksum derived from the very // hash value we're trying to calculate here, so excluding it // avoids a weird circular dependency in the calculation. + + // FFI function to clone a pointer for this object + #[checksum_ignore] + pub(super) ffi_func_clone: FfiFunction, + // FFI function to free a pointer for this object #[checksum_ignore] pub(super) ffi_func_free: FfiFunction, + // Ffi function to initialize the foreign callback for trait interfaces + #[checksum_ignore] + pub(super) ffi_init_callback: Option<FfiFunction>, + #[checksum_ignore] + pub(super) docstring: Option<String>, } impl Object { @@ -118,6 +127,18 @@ impl Object { &self.imp } + pub fn is_trait_interface(&self) -> bool { + self.imp.is_trait_interface() + } + + pub fn has_callback_interface(&self) -> bool { + self.imp.has_callback_interface() + } + + pub fn has_async_method(&self) -> bool { + self.methods.iter().any(Method::is_async) + } + pub fn constructors(&self) -> Vec<&Constructor> { self.constructors.iter().collect() } @@ -151,12 +172,28 @@ impl Object { self.uniffi_traits.iter().collect() } + pub fn ffi_object_clone(&self) -> &FfiFunction { + &self.ffi_func_clone + } + pub fn ffi_object_free(&self) -> &FfiFunction { &self.ffi_func_free } + pub fn ffi_init_callback(&self) -> &FfiFunction { + self.ffi_init_callback + .as_ref() + .unwrap_or_else(|| panic!("No ffi_init_callback set for {}", &self.name)) + } + + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> { - iter::once(&self.ffi_func_free) + [&self.ffi_func_clone, &self.ffi_func_free] + .into_iter() + .chain(&self.ffi_init_callback) .chain(self.constructors.iter().map(|f| &f.ffi_func)) .chain(self.methods.iter().map(|f| &f.ffi_func)) .chain( @@ -173,13 +210,26 @@ impl Object { } pub fn derive_ffi_funcs(&mut self) -> Result<()> { + assert!(!self.ffi_func_clone.name().is_empty()); assert!(!self.ffi_func_free.name().is_empty()); + self.ffi_func_clone.arguments = vec![FfiArgument { + name: "ptr".to_string(), + type_: FfiType::RustArcPtr(self.name.to_string()), + }]; + self.ffi_func_clone.return_type = Some(FfiType::RustArcPtr(self.name.to_string())); self.ffi_func_free.arguments = vec![FfiArgument { name: "ptr".to_string(), type_: FfiType::RustArcPtr(self.name.to_string()), }]; self.ffi_func_free.return_type = None; self.ffi_func_free.is_object_free_function = true; + if self.has_callback_interface() { + self.ffi_init_callback = Some(FfiFunction::callback_init( + &self.module_path, + &self.name, + callbacks::vtable_name(&self.name), + )); + } for cons in self.constructors.iter_mut() { cons.derive_ffi_func(); @@ -194,6 +244,41 @@ impl Object { Ok(()) } + /// For trait interfaces, FfiCallbacks to define for our methods, otherwise an empty vec. + pub fn ffi_callbacks(&self) -> Vec<FfiCallbackFunction> { + if self.is_trait_interface() { + callbacks::ffi_callbacks(&self.name, &self.methods) + } else { + vec![] + } + } + + /// For trait interfaces, the VTable FFI type + pub fn vtable(&self) -> Option<FfiType> { + self.is_trait_interface() + .then(|| FfiType::Struct(callbacks::vtable_name(&self.name))) + } + + /// For trait interfaces, the VTable struct to define. Otherwise None. + pub fn vtable_definition(&self) -> Option<FfiStruct> { + self.is_trait_interface() + .then(|| callbacks::vtable_struct(&self.name, &self.methods)) + } + + /// Vec of (ffi_callback_name, method) pairs + pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> { + self.methods + .iter() + .enumerate() + .map(|(i, method)| { + ( + callbacks::method_ffi_callback(&self.name, method, i), + method, + ) + }) + .collect() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new( self.methods @@ -218,6 +303,7 @@ impl AsType for Object { impl From<uniffi_meta::ObjectMetadata> for Object { fn from(meta: uniffi_meta::ObjectMetadata) -> Self { + let ffi_clone_name = meta.clone_ffi_symbol_name(); let ffi_free_name = meta.free_ffi_symbol_name(); Object { module_path: meta.module_path, @@ -226,10 +312,16 @@ impl From<uniffi_meta::ObjectMetadata> for Object { constructors: Default::default(), methods: Default::default(), uniffi_traits: Default::default(), + ffi_func_clone: FfiFunction { + name: ffi_clone_name, + ..Default::default() + }, ffi_func_free: FfiFunction { name: ffi_free_name, ..Default::default() }, + ffi_init_callback: None, + docstring: meta.docstring.clone(), } } } @@ -263,6 +355,7 @@ pub struct Constructor { pub(super) name: String, pub(super) object_name: String, pub(super) object_module_path: String, + pub(super) is_async: bool, pub(super) arguments: Vec<Argument>, // We don't include the FFIFunc in the hash calculation, because: // - it is entirely determined by the other fields, @@ -272,6 +365,8 @@ pub struct Constructor { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option<String>, pub(super) throws: Option<Type>, pub(super) checksum_fn_name: String, // Force a checksum value, or we'll fallback to the trait. @@ -316,14 +411,20 @@ impl Constructor { self.throws.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn is_primary_constructor(&self) -> bool { self.name == "new" } fn derive_ffi_func(&mut self) { assert!(!self.ffi_func.name().is_empty()); - self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); - self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone())); + self.ffi_func.init( + Some(FfiType::RustArcPtr(self.object_name.clone())), + self.arguments.iter().map(Into::into), + ); } pub fn iter_types(&self) -> TypeIterator<'_> { @@ -339,14 +440,17 @@ impl From<uniffi_meta::ConstructorMetadata> for Constructor { let ffi_func = FfiFunction { name: ffi_name, + is_async: meta.is_async, ..FfiFunction::default() }; Self { name: meta.name, object_name: meta.self_name, + is_async: meta.is_async, object_module_path: meta.module_path, arguments, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), checksum_fn_name, checksum: meta.checksum, @@ -375,6 +479,8 @@ pub struct Method { // avoids a weird circular dependency in the calculation. #[checksum_ignore] pub(super) ffi_func: FfiFunction, + #[checksum_ignore] + pub(super) docstring: Option<String>, pub(super) throws: Option<Type>, pub(super) takes_self_by_arc: bool, pub(super) checksum_fn_name: String, @@ -445,6 +551,10 @@ impl Method { self.throws.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn takes_self_by_arc(&self) -> bool { self.takes_self_by_arc } @@ -466,6 +576,11 @@ impl Method { .chain(self.return_type.iter().flat_map(Type::iter_types)), ) } + + /// For async callback interface methods, the FFI struct to pass to the completion function. + pub fn foreign_future_ffi_result_struct(&self) -> FfiStruct { + callbacks::foreign_future_ffi_result_struct(self.return_type.as_ref().map(FfiType::from)) + } } impl From<uniffi_meta::MethodMetadata> for Method { @@ -491,6 +606,7 @@ impl From<uniffi_meta::MethodMetadata> for Method { arguments, return_type, ffi_func, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), takes_self_by_arc: meta.takes_self_by_arc, checksum_fn_name, @@ -503,19 +619,22 @@ impl From<uniffi_meta::TraitMethodMetadata> for Method { fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self { let ffi_name = meta.ffi_symbol_name(); let checksum_fn_name = meta.checksum_symbol_name(); + let is_async = meta.is_async; let return_type = meta.return_type.map(Into::into); let arguments = meta.inputs.into_iter().map(Into::into).collect(); let ffi_func = FfiFunction { name: ffi_name, + is_async, ..FfiFunction::default() }; Self { name: meta.name, object_name: meta.trait_name, object_module_path: meta.module_path, - is_async: false, + is_async, arguments, return_type, + docstring: meta.docstring.clone(), throws: meta.throws.map(Into::into), takes_self_by_arc: meta.takes_self_by_arc, checksum_fn_name, @@ -583,7 +702,7 @@ impl Callable for Constructor { } fn is_async(&self) -> bool { - false + self.is_async } } @@ -603,6 +722,10 @@ impl Callable for Method { fn is_async(&self) -> bool { self.is_async } + + fn takes_self(&self) -> bool { + true + } } #[cfg(test)] @@ -770,4 +893,62 @@ mod test { "Trait interfaces can not have constructors: \"new\"" ); } + + #[test] + fn test_docstring_object() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + interface Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_constructor() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + /// informative docstring + constructor(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .primary_constructor() + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_method() { + const UDL: &str = r#" + namespace test{}; + interface Testing { + /// informative docstring + void testing(); + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_object_definition("Testing") + .unwrap() + .get_method("testing") + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/record.rs b/third_party/rust/uniffi_bindgen/src/interface/record.rs index 17d3774a49..e9a6004189 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/record.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs @@ -60,6 +60,8 @@ pub struct Record { pub(super) name: String, pub(super) module_path: String, pub(super) fields: Vec<Field>, + #[checksum_ignore] + pub(super) docstring: Option<String>, } impl Record { @@ -71,9 +73,17 @@ impl Record { &self.fields } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { Box::new(self.fields.iter().flat_map(Field::iter_types)) } + + pub fn has_fields(&self) -> bool { + !self.fields.is_empty() + } } impl AsType for Record { @@ -97,6 +107,7 @@ impl TryFrom<uniffi_meta::RecordMetadata> for Record { .into_iter() .map(TryInto::try_into) .collect::<Result<_>>()?, + docstring: meta.docstring.clone(), }) } } @@ -107,6 +118,8 @@ pub struct Field { pub(super) name: String, pub(super) type_: Type, pub(super) default: Option<Literal>, + #[checksum_ignore] + pub(super) docstring: Option<String>, } impl Field { @@ -118,6 +131,10 @@ impl Field { self.default.as_ref() } + pub fn docstring(&self) -> Option<&str> { + self.docstring.as_deref() + } + pub fn iter_types(&self) -> TypeIterator<'_> { self.type_.iter_types() } @@ -140,6 +157,7 @@ impl TryFrom<uniffi_meta::FieldMetadata> for Field { name, type_, default, + docstring: meta.docstring.clone(), }) } } @@ -227,4 +245,39 @@ mod test { .iter_types() .any(|t| matches!(t, Type::Record { name, .. } if name == "Testing"))); } + + #[test] + fn test_docstring_record() { + const UDL: &str = r#" + namespace test{}; + /// informative docstring + dictionary Testing { }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_record_definition("Testing") + .unwrap() + .docstring() + .unwrap(), + "informative docstring" + ); + } + + #[test] + fn test_docstring_record_field() { + const UDL: &str = r#" + namespace test{}; + dictionary Testing { + /// informative docstring + i32 testing; + }; + "#; + let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap(); + assert_eq!( + ci.get_record_definition("Testing").unwrap().fields()[0] + .docstring() + .unwrap(), + "informative docstring" + ); + } } diff --git a/third_party/rust/uniffi_bindgen/src/interface/universe.rs b/third_party/rust/uniffi_bindgen/src/interface/universe.rs index e69d86e44f..70bc61f8a9 100644 --- a/third_party/rust/uniffi_bindgen/src/interface/universe.rs +++ b/third_party/rust/uniffi_bindgen/src/interface/universe.rs @@ -25,6 +25,7 @@ pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type, pub(crate) struct TypeUniverse { /// The unique prefixes that we'll use for namespacing when exposing this component's API. pub namespace: NamespaceMetadata, + pub namespace_docstring: Option<String>, // Named type definitions (including aliases). type_definitions: HashMap<String, Type>, @@ -83,9 +84,6 @@ impl TypeUniverse { Type::Bytes => self.add_type_definition("bytes", type_)?, Type::Timestamp => self.add_type_definition("timestamp", type_)?, Type::Duration => self.add_type_definition("duration", type_)?, - Type::ForeignExecutor => { - self.add_type_definition("ForeignExecutor", type_)?; - } Type::Object { name, .. } | Type::Record { name, .. } | Type::Enum { name, .. } @@ -118,6 +116,7 @@ impl TypeUniverse { Ok(()) } + #[cfg(test)] /// Check if a [Type] is present pub fn contains(&self, type_: &Type) -> bool { self.all_known_types.contains(type_) diff --git a/third_party/rust/uniffi_bindgen/src/lib.rs b/third_party/rust/uniffi_bindgen/src/lib.rs index 019b24022f..dfc90b32a6 100644 --- a/third_party/rust/uniffi_bindgen/src/lib.rs +++ b/third_party/rust/uniffi_bindgen/src/lib.rs @@ -58,9 +58,8 @@ //! //! ### 3) Generate and include component scaffolding from the UDL file //! -//! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`. -//! Then add to your crate `uniffi_build` under `[build-dependencies]`. -//! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding` +//! Add to your crate `uniffi_build` under `[build-dependencies]`, +//! then add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding` //! to process your `.udl` file. This will generate some Rust code to be included in the top-level source //! code of your crate. If your UDL file is named `example.udl`, then your build script would call: //! @@ -77,12 +76,13 @@ //! //! ### 4) Generate foreign language bindings for the library //! -//! The `uniffi-bindgen` utility provides a command-line tool that can produce code to +//! You will need ensure a local `uniffi-bindgen` - see <https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html> +//! This utility provides a command-line tool that can produce code to //! consume the Rust library in any of several supported languages. //! It is done by calling (in kotlin for example): //! //! ```text -//! uniffi-bindgen --language kotlin ./src/example.udl +//! cargo run --bin -p uniffi-bindgen --language kotlin ./src/example.udl //! ``` //! //! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings @@ -160,15 +160,16 @@ pub trait BindingGenerator: Sized { ci: &ComponentInterface, config: &Self::Config, out_dir: &Utf8Path, + try_format_code: bool, ) -> Result<()>; /// Check if `library_path` used by library mode is valid for this generator fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>; } -struct BindingGeneratorDefault { - target_languages: Vec<TargetLanguage>, - try_format_code: bool, +pub struct BindingGeneratorDefault { + pub target_languages: Vec<TargetLanguage>, + pub try_format_code: bool, } impl BindingGenerator for BindingGeneratorDefault { @@ -179,6 +180,7 @@ impl BindingGenerator for BindingGeneratorDefault { ci: &ComponentInterface, config: &Self::Config, out_dir: &Utf8Path, + _try_format_code: bool, ) -> Result<()> { for &language in &self.target_languages { bindings::write_bindings( @@ -219,12 +221,13 @@ impl BindingGenerator for BindingGeneratorDefault { /// - `library_file`: The path to a dynamic library to attempt to extract the definitions from and extend the component interface with. No extensions to component interface occur if it's [`None`] /// - `crate_name`: Override the default crate name that is guessed from UDL file path. pub fn generate_external_bindings<T: BindingGenerator>( - binding_generator: T, + binding_generator: &T, udl_file: impl AsRef<Utf8Path>, config_file_override: Option<impl AsRef<Utf8Path>>, out_dir_override: Option<impl AsRef<Utf8Path>>, library_file: Option<impl AsRef<Utf8Path>>, crate_name: Option<&str>, + try_format_code: bool, ) -> Result<()> { let crate_name = crate_name .map(|c| Ok(c.to_string())) @@ -253,7 +256,7 @@ pub fn generate_external_bindings<T: BindingGenerator>( udl_file.as_ref(), out_dir_override.as_ref().map(|p| p.as_ref()), )?; - binding_generator.write_bindings(&component, &config, &out_dir) + binding_generator.write_bindings(&component, &config, &out_dir, try_format_code) } // Generate the infrastructural Rust code for implementing the UDL interface, @@ -301,25 +304,23 @@ fn generate_component_scaffolding_inner( // Generate the bindings in the target languages that call the scaffolding // Rust code. -pub fn generate_bindings( +pub fn generate_bindings<T: BindingGenerator>( udl_file: &Utf8Path, config_file_override: Option<&Utf8Path>, - target_languages: Vec<TargetLanguage>, + binding_generator: T, out_dir_override: Option<&Utf8Path>, library_file: Option<&Utf8Path>, crate_name: Option<&str>, try_format_code: bool, ) -> Result<()> { generate_external_bindings( - BindingGeneratorDefault { - target_languages, - try_format_code, - }, + &binding_generator, udl_file, config_file_override, out_dir_override, library_file, crate_name, + try_format_code, ) } @@ -417,22 +418,53 @@ fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> { Ok(()) } +/// Load TOML from file if the file exists. +fn load_toml_file(source: Option<&Utf8Path>) -> Result<Option<toml::value::Table>> { + if let Some(source) = source { + if source.exists() { + let contents = + fs::read_to_string(source).with_context(|| format!("read file: {:?}", source))?; + return Ok(Some( + toml::de::from_str(&contents) + .with_context(|| format!("parse toml: {:?}", source))?, + )); + } + } + + Ok(None) +} + +/// Load the default `uniffi.toml` config, merge TOML trees with `config_file_override` if specified. fn load_initial_config<Config: DeserializeOwned>( crate_root: &Utf8Path, config_file_override: Option<&Utf8Path>, ) -> Result<Config> { - let path = match config_file_override { - Some(cfg) => Some(cfg.to_owned()), - None => crate_root.join("uniffi.toml").canonicalize_utf8().ok(), - }; - let toml_config = match path { - Some(path) => { - let contents = fs::read_to_string(path).context("Failed to read config file")?; - toml::de::from_str(&contents)? + let mut config = load_toml_file(Some(crate_root.join("uniffi.toml").as_path())) + .context("default config")? + .unwrap_or(toml::value::Table::default()); + + let override_config = load_toml_file(config_file_override).context("override config")?; + if let Some(override_config) = override_config { + merge_toml(&mut config, override_config); + } + + Ok(toml::Value::from(config).try_into()?) +} + +fn merge_toml(a: &mut toml::value::Table, b: toml::value::Table) { + for (key, value) in b.into_iter() { + match a.get_mut(&key) { + Some(existing_value) => match (existing_value, value) { + (toml::Value::Table(ref mut t0), toml::Value::Table(t1)) => { + merge_toml(t0, t1); + } + (v, value) => *v = value, + }, + None => { + a.insert(key, value); + } } - None => toml::Value::from(toml::value::Table::default()), - }; - Ok(toml_config.try_into()?) + } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -516,4 +548,56 @@ mod test { let not_a_crate_root = &this_crate_root.join("src/templates"); assert!(guess_crate_root(¬_a_crate_root.join("src/example.udl")).is_err()); } + + #[test] + fn test_merge_toml() { + let default = r#" + foo = "foo" + bar = "bar" + + [table1] + foo = "foo" + bar = "bar" + "#; + let mut default = toml::de::from_str(default).unwrap(); + + let override_toml = r#" + # update key + bar = "BAR" + # insert new key + baz = "BAZ" + + [table1] + # update key + bar = "BAR" + # insert new key + baz = "BAZ" + + # new table + [table1.table2] + bar = "BAR" + baz = "BAZ" + "#; + let override_toml = toml::de::from_str(override_toml).unwrap(); + + let expected = r#" + foo = "foo" + bar = "BAR" + baz = "BAZ" + + [table1] + foo = "foo" + bar = "BAR" + baz = "BAZ" + + [table1.table2] + bar = "BAR" + baz = "BAZ" + "#; + let expected: toml::value::Table = toml::de::from_str(expected).unwrap(); + + merge_toml(&mut default, override_toml); + + assert_eq!(&expected, &default); + } } diff --git a/third_party/rust/uniffi_bindgen/src/library_mode.rs b/third_party/rust/uniffi_bindgen/src/library_mode.rs index f170ea5e91..c460c03d9f 100644 --- a/third_party/rust/uniffi_bindgen/src/library_mode.rs +++ b/third_party/rust/uniffi_bindgen/src/library_mode.rs @@ -16,8 +16,8 @@ /// - UniFFI can figure out the package/module names for each crate, eliminating the external /// package maps. use crate::{ - bindings::TargetLanguage, load_initial_config, macro_metadata, BindingGenerator, - BindingGeneratorDefault, BindingsConfig, ComponentInterface, Result, + load_initial_config, macro_metadata, BindingGenerator, BindingsConfig, ComponentInterface, + Result, }; use anyhow::{bail, Context}; use camino::Utf8Path; @@ -33,21 +33,21 @@ use uniffi_meta::{ /// Generate foreign bindings /// /// Returns the list of sources used to generate the bindings, in no particular order. -pub fn generate_bindings( +pub fn generate_bindings<T: BindingGenerator + ?Sized>( library_path: &Utf8Path, crate_name: Option<String>, - target_languages: &[TargetLanguage], + binding_generator: &T, + config_file_override: Option<&Utf8Path>, out_dir: &Utf8Path, try_format_code: bool, -) -> Result<Vec<Source<crate::Config>>> { +) -> Result<Vec<Source<T::Config>>> { generate_external_bindings( - BindingGeneratorDefault { - target_languages: target_languages.into(), - try_format_code, - }, + binding_generator, library_path, - crate_name, + crate_name.clone(), + config_file_override, out_dir, + try_format_code, ) } @@ -55,10 +55,12 @@ pub fn generate_bindings( /// /// Returns the list of sources used to generate the bindings, in no particular order. pub fn generate_external_bindings<T: BindingGenerator>( - binding_generator: T, + binding_generator: &T, library_path: &Utf8Path, crate_name: Option<String>, + config_file_override: Option<&Utf8Path>, out_dir: &Utf8Path, + try_format_code: bool, ) -> Result<Vec<Source<T::Config>>> { let cargo_metadata = MetadataCommand::new() .exec() @@ -66,7 +68,12 @@ pub fn generate_external_bindings<T: BindingGenerator>( let cdylib_name = calc_cdylib_name(library_path); binding_generator.check_library_path(library_path, cdylib_name)?; - let mut sources = find_sources(&cargo_metadata, library_path, cdylib_name)?; + let mut sources = find_sources( + &cargo_metadata, + library_path, + cdylib_name, + config_file_override, + )?; for i in 0..sources.len() { // Partition up the sources list because we're eventually going to call // `update_from_dependency_configs()` which requires an exclusive reference to one source and @@ -101,7 +108,7 @@ pub fn generate_external_bindings<T: BindingGenerator>( } for source in sources.iter() { - binding_generator.write_bindings(&source.ci, &source.config, out_dir)?; + binding_generator.write_bindings(&source.ci, &source.config, out_dir, try_format_code)?; } Ok(sources) @@ -118,10 +125,10 @@ pub struct Source<Config: BindingsConfig> { // If `library_path` is a C dynamic library, return its name pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> { - let cdylib_extentions = [".so", ".dll", ".dylib"]; + let cdylib_extensions = [".so", ".dll", ".dylib"]; let filename = library_path.file_name()?; let filename = filename.strip_prefix("lib").unwrap_or(filename); - for ext in cdylib_extentions { + for ext in cdylib_extensions { if let Some(f) = filename.strip_suffix(ext) { return Some(f); } @@ -133,6 +140,7 @@ fn find_sources<Config: BindingsConfig>( cargo_metadata: &cargo_metadata::Metadata, library_path: &Utf8Path, cdylib_name: Option<&str>, + config_file_override: Option<&Utf8Path>, ) -> Result<Vec<Source<Config>>> { let items = macro_metadata::extract_from_library(library_path)?; let mut metadata_groups = create_metadata_groups(&items); @@ -178,7 +186,7 @@ fn find_sources<Config: BindingsConfig>( ci.add_metadata(metadata)?; }; ci.add_metadata(group)?; - let mut config = load_initial_config::<Config>(crate_root, None)?; + let mut config = load_initial_config::<Config>(crate_root, config_file_override)?; if let Some(cdylib_name) = cdylib_name { config.update_from_cdylib_name(cdylib_name); } diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs index 7ce6c3a70b..69fad1980e 100644 --- a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs @@ -4,9 +4,7 @@ use crate::interface::{CallbackInterface, ComponentInterface, Enum, Record, Type}; use anyhow::{bail, Context}; -use uniffi_meta::{ - create_metadata_groups, group_metadata, EnumMetadata, ErrorMetadata, Metadata, MetadataGroup, -}; +use uniffi_meta::{create_metadata_groups, group_metadata, EnumMetadata, Metadata, MetadataGroup}; /// Add Metadata items to the ComponentInterface /// @@ -98,7 +96,9 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res iface.add_record_definition(record)?; } Metadata::Enum(meta) => { - let flat = meta.variants.iter().all(|v| v.fields.is_empty()); + let flat = meta + .forced_flatness + .unwrap_or_else(|| meta.variants.iter().all(|v| v.fields.is_empty())); add_enum_to_ci(iface, meta, flat)?; } Metadata::Object(meta) => { @@ -117,22 +117,11 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res module_path: meta.module_path.clone(), name: meta.name.clone(), })?; - iface.add_callback_interface_definition(CallbackInterface::new( - meta.name, - meta.module_path, - )); + iface.add_callback_interface_definition(CallbackInterface::try_from(meta)?); } Metadata::TraitMethod(meta) => { iface.add_trait_method_meta(meta)?; } - Metadata::Error(meta) => { - iface.note_name_used_as_error(meta.name()); - match meta { - ErrorMetadata::Enum { enum_, is_flat } => { - add_enum_to_ci(iface, enum_, is_flat)?; - } - }; - } Metadata::CustomType(meta) => { iface.types.add_known_type(&Type::Custom { module_path: meta.module_path.clone(), diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs index 25b5ef17ba..6d440919f1 100644 --- a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs +++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs @@ -30,7 +30,7 @@ fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> { Object::PE(pe) => extract_from_pe(pe, file_data), Object::Mach(mach) => extract_from_mach(mach, file_data), Object::Archive(archive) => extract_from_archive(archive, file_data), - Object::Unknown(_) => bail!("Unknown library format"), + _ => bail!("Unknown library format"), } } diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs index f3759cf6fa..7fd81831aa 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs @@ -45,7 +45,6 @@ mod filters { format!("std::sync::Arc<{}>", imp.rust_name_for(name)) } Type::CallbackInterface { name, .. } => format!("Box<dyn r#{name}>"), - Type::ForeignExecutor => "::uniffi::ForeignExecutor".into(), Type::Optional { inner_type } => { format!("std::option::Option<{}>", type_rs(inner_type)?) } @@ -64,41 +63,12 @@ mod filters { kind: ExternalKind::Interface, .. } => format!("::std::sync::Arc<r#{name}>"), - Type::External { name, .. } => format!("r#{name}"), - }) - } - - // Map a type to Rust code that specifies the FfiConverter implementation. - // - // This outputs something like `<MyStruct as Lift<crate::UniFfiTag>>` - pub fn ffi_trait(type_: &Type, trait_name: &str) -> Result<String, askama::Error> { - Ok(match type_ { Type::External { name, - kind: ExternalKind::Interface, + kind: ExternalKind::Trait, .. - } => { - format!("<::std::sync::Arc<r#{name}> as ::uniffi::{trait_name}<crate::UniFfiTag>>") - } - _ => format!( - "<{} as ::uniffi::{trait_name}<crate::UniFfiTag>>", - type_rs(type_)? - ), - }) - } - - pub fn return_type<T: Callable>(callable: &T) -> Result<String, askama::Error> { - let return_type = match callable.return_type() { - Some(t) => type_rs(&t)?, - None => "()".to_string(), - }; - match callable.throws_type() { - Some(t) => type_rs(&t)?, - None => "()".to_string(), - }; - Ok(match callable.throws_type() { - Some(e) => format!("::std::result::Result<{return_type}, {}>", type_rs(&e)?), - None => return_type, + } => format!("::std::sync::Arc<dyn r#{name}>"), + Type::External { name, .. } => format!("r#{name}"), }) } diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs index 64c69e4d8e..658f4c8de5 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs @@ -1,82 +1,17 @@ -{# -// For each Callback Interface definition, we assume that there is a corresponding trait defined in Rust client code. -// If the UDL callback interface and Rust trait's methods don't match, the Rust compiler will complain. -// We generate: -// * an init function to accept that `ForeignCallback` from the foreign language, and stores it. -// * a holder for a `ForeignCallback`, of type `uniffi::ForeignCallbackInternals`. -// * a proxy `struct` which implements the `trait` that the Callback Interface corresponds to. This -// is the object that client code interacts with. -// - for each method, arguments will be packed into a `RustBuffer` and sent over the `ForeignCallback` to be -// unpacked and called. The return value is packed into another `RustBuffer` and sent back to Rust. -// - a `Drop` `impl`, which tells the foreign language to forget about the real callback object. -#} -{% let trait_name = cbi.name() -%} -{% let trait_impl = format!("UniFFICallbackHandler{}", trait_name) %} -{% let foreign_callback_internals = format!("foreign_callback_{}_internals", trait_name)|upper -%} - -// Register a foreign callback for getting across the FFI. -#[doc(hidden)] -static {{ foreign_callback_internals }}: uniffi::ForeignCallbackInternals = uniffi::ForeignCallbackInternals::new(); - -#[doc(hidden)] -#[no_mangle] -pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::ForeignCallback, _: &mut uniffi::RustCallStatus) { - {{ foreign_callback_internals }}.set_callback(callback); - // The call status should be initialized to CALL_SUCCESS, so no need to modify it. -} - -// Make an implementation which will shell out to the foreign language. -#[doc(hidden)] -#[derive(Debug)] -struct {{ trait_impl }} { - handle: u64 -} - -impl {{ trait_impl }} { - fn new(handle: u64) -> Self { - Self { handle } - } -} - -impl Drop for {{ trait_impl }} { - fn drop(&mut self) { - {{ foreign_callback_internals }}.invoke_callback::<(), crate::UniFfiTag>( - self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() - ) - } -} - -uniffi::deps::static_assertions::assert_impl_all!({{ trait_impl }}: Send); - -impl r#{{ trait_name }} for {{ trait_impl }} { +#[::uniffi::export_for_udl(callback_interface)] +pub trait r#{{ cbi.name() }} { {%- for meth in cbi.methods() %} - - {#- Method declaration #} - fn r#{{ meth.name() -}} - ({% call rs::arg_list_decl_with_prefix("&self", meth) %}) - {%- match (meth.return_type(), meth.throws_type()) %} - {%- when (Some(return_type), None) %} -> {{ return_type.borrow()|type_rs }} - {%- when (Some(return_type), Some(err)) %} -> ::std::result::Result<{{ return_type.borrow()|type_rs }}, {{ err|type_rs }}> - {%- when (None, Some(err)) %} -> ::std::result::Result<(), {{ err|type_rs }}> - {% else -%} - {%- endmatch -%} { - {#- Method body #} - - {#- Packing args into a RustBuffer #} - {% if meth.arguments().len() == 0 -%} - let args_buf = Vec::new(); - {% else -%} - let mut args_buf = Vec::new(); - {% endif -%} + fn r#{{ meth.name() }}( + {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %}, {%- for arg in meth.arguments() %} - {{ arg.as_type().borrow()|ffi_trait("Lower") }}::write(r#{{ arg.name() }}, &mut args_buf); - {%- endfor -%} - let args_rbuf = uniffi::RustBuffer::from_vec(args_buf); - - {#- Calling into foreign code. #} - {{ foreign_callback_internals }}.invoke_callback::<{{ meth|return_type }}, crate::UniFfiTag>(self.handle, {{ loop.index }}, args_rbuf) - } - {%- endfor %} + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + {%- endfor %} + ) + {%- match (meth.return_type(), meth.throws_type()) %} + {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }}; + {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}>; + {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}>; + {%- when (None, None) %}; + {%- endmatch %} + {% endfor %} } - -::uniffi::scaffolding_ffi_converter_callback_interface!(r#{{ trait_name }}, {{ trait_impl }}); diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs index 6b9f96f224..f918ef2f3a 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs @@ -1,13 +1,12 @@ {# -// For each enum declared in the UDL, we assume the caller has provided a corresponding -// rust `enum`. We provide the traits for sending it across the FFI, which will fail to -// compile if the provided struct has a different shape to the one declared in the UDL. -// -// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's -// public so other crates can refer to it via an `[External='crate'] typedef` +// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} -#[::uniffi::derive_enum_for_udl] +#[::uniffi::derive_enum_for_udl( + {%- if e.is_non_exhaustive() -%} + non_exhaustive, + {%- endif %} +)] enum r#{{ e.name() }} { {%- for variant in e.variants() %} r#{{ variant.name() }} { diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs index 94538ecaa8..64f48e2334 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs @@ -1,10 +1,5 @@ {# -// For each error declared in the UDL, we assume the caller has provided a corresponding -// rust `enum`. We provide the traits for sending it across the FFI, which will fail to -// compile if the provided struct has a different shape to the one declared in the UDL. -// -// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's -// public so other crates can refer to it via an `[External='crate'] typedef` +// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} #[::uniffi::derive_error_for_udl( @@ -14,6 +9,9 @@ with_try_read, {%- endif %} {%- endif %} + {%- if e.is_non_exhaustive() -%} + non_exhaustive, + {%- endif %} )] enum r#{{ e.name() }} { {%- for variant in e.variants() %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs index ade1578897..d67e172cc2 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs @@ -10,6 +10,8 @@ ::uniffi::ffi_converter_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); {%- when ExternalKind::Interface %} ::uniffi::ffi_converter_arc_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); +{%- when ExternalKind::Trait %} +::uniffi::ffi_converter_arc_forward!(dyn r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag); {%- endmatch %} {% endif %} {%- endfor %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs index e2445c670d..e752878af5 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs @@ -1,24 +1,15 @@ -// For each Object definition, we assume the caller has provided an appropriately-shaped `struct T` -// with an `impl` for each method on the object. We create an `Arc<T>` for "safely" handing out -// references to these structs to foreign language code, and we provide a `pub extern "C"` function -// corresponding to each method. -// -// (Note that "safely" is in "scare quotes" - that's because we use functions on an `Arc` that -// that are inherently unsafe, but the code we generate is safe in practice.) -// -// If the caller's implementation of the struct does not match with the methods or types specified -// in the UDL, then the rust compiler will complain with a (hopefully at least somewhat helpful!) -// error message when processing this generated code. +{# +// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. +#} -{%- match obj.imp() -%} -{%- when ObjectImpl::Trait %} -#[::uniffi::export_for_udl] +{%- if obj.is_trait_interface() %} +#[::uniffi::export_for_udl{% if obj.has_callback_interface() %}(with_foreign){% endif %}] pub trait r#{{ obj.name() }} { {%- for meth in obj.methods() %} - fn {{ meth.name() }}( + {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}( {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %}, {%- for arg in meth.arguments() %} - {{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, + r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, {%- endfor %} ) {%- match (meth.return_type(), meth.throws_type()) %} @@ -29,7 +20,7 @@ pub trait r#{{ obj.name() }} { {%- endmatch %} {% endfor %} } -{% when ObjectImpl::Struct %} +{%- else %} {%- for tm in obj.uniffi_traits() %} {% match tm %} {% when UniffiTrait::Debug { fmt }%} @@ -46,9 +37,10 @@ pub trait r#{{ obj.name() }} { struct {{ obj.rust_name() }} { } {%- for cons in obj.constructors() %} -#[::uniffi::export_for_udl(constructor)] +#[::uniffi::export_for_udl] impl {{ obj.rust_name() }} { - pub fn r#{{ cons.name() }}( + #[uniffi::constructor] + pub {% if cons.is_async() %}async {% endif %}fn r#{{ cons.name() }}( {%- for arg in cons.arguments() %} r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, {%- endfor %} @@ -68,7 +60,7 @@ impl {{ obj.rust_name() }} { {%- for meth in obj.methods() %} #[::uniffi::export_for_udl] impl {{ obj.rust_name() }} { - pub fn r#{{ meth.name() }}( + pub {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}( {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %}, {%- for arg in meth.arguments() %} r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, @@ -86,4 +78,4 @@ impl {{ obj.rust_name() }} { } {%- endfor %} -{% endmatch %} +{% endif %} diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs index 85e131dd8c..a7affdf7b8 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs @@ -1,11 +1,5 @@ {# -// For each record declared in the UDL, we assume the caller has provided a corresponding -// rust `struct` with the declared fields. We provide the traits for sending it across the FFI. -// If the caller's struct does not match the shape and types declared in the UDL then the rust -// compiler will complain with a type error. -// -// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's -// public so other crates can refer to it via an `[External='crate'] typedef` +// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. #} #[::uniffi::derive_record_for_udl] diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs index eeee0f5ee2..27f3686b9f 100644 --- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs +++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs @@ -1,5 +1,8 @@ +{# +// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent. +#} #[::uniffi::export_for_udl] -pub fn r#{{ func.name() }}( +pub {% if func.is_async() %}async {% endif %}fn r#{{ func.name() }}( {%- for arg in func.arguments() %} r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }}, {%- endfor %} diff --git a/third_party/rust/uniffi_build/.cargo-checksum.json b/third_party/rust/uniffi_build/.cargo-checksum.json index 8e585bfa95..a9005a0f58 100644 --- a/third_party/rust/uniffi_build/.cargo-checksum.json +++ b/third_party/rust/uniffi_build/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"a6db989e5a3d597219df0a9c94541130b7db607efd8606043cd1187971020639","src/lib.rs":"47ff3d1a18456164414af1c20cd5df129401e5257cc15552ecc39afed8970707"},"package":"001964dd3682d600084b3aaf75acf9c3426699bc27b65e96bb32d175a31c74e9"}
\ No newline at end of file +{"files":{"Cargo.toml":"8fcf43ff5e6c1281a1ee5f9ed796b0f8115bd39ca9ce5b2d0c32e88d9eb2038f","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/lib.rs":"47ff3d1a18456164414af1c20cd5df129401e5257cc15552ecc39afed8970707"},"package":"45cba427aeb7b3a8b54830c4c915079a7a3c62608dd03dddba1d867a8a023eb4"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_build/Cargo.toml b/third_party/rust/uniffi_build/Cargo.toml index 3fe7ee5cf0..fed51c34ca 100644 --- a/third_party/rust/uniffi_build/Cargo.toml +++ b/third_party/rust/uniffi_build/Cargo.toml @@ -12,11 +12,12 @@ [package] edition = "2021" name = "uniffi_build" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust (build script helpers)" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -31,7 +32,7 @@ version = "1" version = "1.0.8" [dependencies.uniffi_bindgen] -version = "=0.25.3" +version = "=0.27.1" default-features = false [features] diff --git a/third_party/rust/uniffi_build/README.md b/third_party/rust/uniffi_build/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_build/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json b/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json index 9df6eba7b4..5e211378a0 100644 --- a/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json +++ b/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"028104be15a1d73a8ca192a30865b26827344808a95b21e3798e4c9406ebc4f1","src/lib.rs":"44d2e2c595b14d33d16c71dfe4ef42ad0b9e010a878ee2ec49c2e929d60275ba"},"package":"55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c"}
\ No newline at end of file +{"files":{"Cargo.toml":"da89504b9007c2a1ea0e498a2e8ec6baeb0ff7391363cd9007e383247637792c","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/lib.rs":"44d2e2c595b14d33d16c71dfe4ef42ad0b9e010a878ee2ec49c2e929d60275ba"},"package":"ae7e5a6c33b1dec3f255f57ec0b6af0f0b2bb3021868be1d5eec7a38e2905ebc"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_checksum_derive/Cargo.toml b/third_party/rust/uniffi_checksum_derive/Cargo.toml index a3c4ed7ca3..681c8846e1 100644 --- a/third_party/rust/uniffi_checksum_derive/Cargo.toml +++ b/third_party/rust/uniffi_checksum_derive/Cargo.toml @@ -12,11 +12,12 @@ [package] edition = "2021" name = "uniffi_checksum_derive" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust (checksum custom derive)" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", diff --git a/third_party/rust/uniffi_checksum_derive/README.md b/third_party/rust/uniffi_checksum_derive/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_checksum_derive/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_core/.cargo-checksum.json b/third_party/rust/uniffi_core/.cargo-checksum.json index 59804f7c89..573f7a72c9 100644 --- a/third_party/rust/uniffi_core/.cargo-checksum.json +++ b/third_party/rust/uniffi_core/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"b074f0db902264714faf879e99bdbc07df1550d75694f96751b499f98fecd16d","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"9e8650f0df087bf5e030a13d28f4990079e53613e656789b4b539d937a7fd288","src/ffi/ffidefault.rs":"f1ce099b92adbb12b160d513bae93342c7b6d806d7f6ebb665067db10af9a681","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"af8129a69ef23b92859e1cca0d666c95f0ed2c1fb2797f4495d824b65f774d03","src/ffi/foreignexecutor.rs":"123687921ce6dfb7f5bfa0736a630cfeff7f376b776ea03fc651da21ffd1cab8","src/ffi/mod.rs":"8117b08bbb7af3e97f66ed69c9690b60e8da0d6d8940349c7b9659a47cd8c92f","src/ffi/rustbuffer.rs":"8cc1f94b9ecba52b911da6a68155921c1b7f51b899d9874ddbc281a379941473","src/ffi/rustcalls.rs":"7caaa35ba8898c4b4983f07cefa80584ba00e753a11d496e578c80abe0cabe8b","src/ffi/rustfuture.rs":"d240426c8c8b83e3f6a2c0013e905298611287b2bb2022eb8161532209c635ca","src/ffi_converter_impls.rs":"82c1b47e02718610f2a5556997cd29ba5d8daf149d6353f470be0d9b971d968a","src/ffi_converter_traits.rs":"646c0d4aeb807d3e40db4d289f909030d0b2684087871a7d40d337680096b7d6","src/lib.rs":"4ad1a2899944a20e80a55d1c7bd01ff28395ace743a083c65847e6ea216fc5c8","src/metadata.rs":"6520ffcf2568a0d95f0f854acb6fc8aeaae26ef1f23fc576c2c50db72aa30eee","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"6121a127a3af1665cd90d12dd2b3683c2643c5103281d0fed5838324ca1fad5b"}
\ No newline at end of file +{"files":{"Cargo.toml":"c8969fbc6e8f6694e260ab78c94f9b4195d61afb7836b4c130b542d3b91b9200","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"f0184cf76bd86abb2815d260a87f85bd7060f5373ac6ef6f71955ece2a5075af","src/ffi/ffidefault.rs":"0db83fbcbc274c4c0daf7fb27833400568839b77a3496155840734c511d801e0","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"2b820a34b78705f5debc302a25c64d515a4aa7b3bdade083f4c1cfa2803664ae","src/ffi/foreignfuture.rs":"c1d621e41ea6af0c1d3959b46af8567c3fdc4164e7a82d635fcbb1da2c0737ac","src/ffi/handle.rs":"91f91469a81cb19edebb8bba433df62658cc66f6b54d5dc8520eb5793a85abd9","src/ffi/mod.rs":"30eea545299747838bf11b0698cfb71cedd3ca04d8cfb703c53198fcc44045c1","src/ffi/rustbuffer.rs":"0e725347f916834b17156413f406d5ca6c064b2cbc7437b051fe6692ad72c2aa","src/ffi/rustcalls.rs":"51c6499871c7d5eb4f80cabc806f26dd1df3b1090a2419d0d967aa9c5299a0a6","src/ffi/rustfuture/future.rs":"426cd0ad3c8cf008a7052a7d89856b6c6d5053b94e24325f5666d0281a40ec7f","src/ffi/rustfuture/mod.rs":"44568267e591f5b37f77acfdd6e60ae55ce48ab0a17fd81af3aeb31baa3d53e6","src/ffi/rustfuture/scheduler.rs":"c6484fff14c04596df5f306f2090366435dcff92561d317fde1ea9c097a9576b","src/ffi/rustfuture/tests.rs":"211241fb484a3a103eb0418e7d295850ea021bcd583fa1488f5efc68f33d5ab8","src/ffi_converter_impls.rs":"397c813f2e765462d7a7be524e6ac75e813a91a8ffd11c7e7df05f853213f77b","src/ffi_converter_traits.rs":"24c8cf6ada9b2f63b265e62c0f9092d640e533d0d7234e9156f92c3d1902f430","src/lib.rs":"1f6a031bbb160dfe46455a8bc24596f63b1e478f45579bfff62a62f58900bee4","src/metadata.rs":"83e463c377c0f501e58ac4eb5cc47c433c1473cecd47305fa89283e736b48d96","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"0ea3eb5474d50fc149b7e4d86b9c5bd4a61dcc167f0683902bf18ae7bbb3deef"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_core/Cargo.toml b/third_party/rust/uniffi_core/Cargo.toml index 4d4cbe2758..ce36a2168a 100644 --- a/third_party/rust/uniffi_core/Cargo.toml +++ b/third_party/rust/uniffi_core/Cargo.toml @@ -12,11 +12,12 @@ [package] edition = "2021" name = "uniffi_core" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust (runtime support code)" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -44,7 +45,7 @@ version = "0.4" version = "1.10.0" [dependencies.oneshot] -version = "0.1.5" +version = "0.1.6" features = ["async"] package = "oneshot-uniffi" @@ -56,5 +57,4 @@ version = "1.1.0" [features] default = [] -extern-rustbuffer = [] tokio = ["dep:async-compat"] diff --git a/third_party/rust/uniffi_core/README.md b/third_party/rust/uniffi_core/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_core/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs index 7be66880bb..e7a4faab64 100644 --- a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs +++ b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs @@ -91,121 +91,20 @@ //! //! Uniffi generates a protocol or interface in client code in the foreign language must implement. //! -//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals` -//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface. +//! For each callback interface, UniFFI defines a VTable. +//! This is a `repr(C)` struct where each field is a `repr(C)` callback function pointer. +//! There is one field for each method, plus an extra field for the `uniffi_free` method. +//! The foreign code registers one VTable per callback interface with Rust. //! -//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the -//! `KeychainCallbackInternals` to store the instance in a handlemap. -//! -//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements -//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to -//! client code as `Box<dyn Keychain>`. -//! -//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`. -//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the -//! object handle, and the method selector. -//! -//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap, -//! and calls the actual implementation of the method. -//! -//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for -//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct -//! type and then returns to client code. +//! VTable methods have a similar signature to Rust scaffolding functions. +//! The one difference is that values are returned via an out pointer to work around a Python bug (https://bugs.python.org/issue5710). //! +//! The foreign object that implements the interface is represented by an opaque handle. +//! UniFFI generates a struct that implements the trait by calling VTable methods, passing the handle as the first parameter. +//! When the struct is dropped, the `uniffi_free` method is called. -use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer}; use std::fmt; -/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it, -/// and it can be deleted from the handle map. -pub const IDX_CALLBACK_FREE: u32 = 0; - -/// Result of a foreign callback invocation -#[repr(i32)] -#[derive(Debug, PartialEq, Eq)] -pub enum CallbackResult { - /// Successful call. - /// The return value is serialized to `buf_ptr`. - Success = 0, - /// Expected error. - /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result. - /// The error value is serialized to `buf_ptr`. - Error = 1, - /// Unexpected error. - /// An error message string is serialized to `buf_ptr`. - UnexpectedError = 2, -} - -impl TryFrom<i32> for CallbackResult { - // On errors we return the unconverted value - type Error = i32; - - fn try_from(value: i32) -> Result<Self, i32> { - match value { - 0 => Ok(Self::Success), - 1 => Ok(Self::Error), - 2 => Ok(Self::UnexpectedError), - n => Err(n), - } - } -} - -/// Struct to hold a foreign callback. -pub struct ForeignCallbackInternals { - callback_cell: ForeignCallbackCell, -} - -impl ForeignCallbackInternals { - pub const fn new() -> Self { - ForeignCallbackInternals { - callback_cell: ForeignCallbackCell::new(), - } - } - - pub fn set_callback(&self, callback: ForeignCallback) { - self.callback_cell.set(callback); - } - - /// Invoke a callback interface method on the foreign side and return the result - pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R - where - R: LiftReturn<UniFfiTag>, - { - let mut ret_rbuf = RustBuffer::new(); - let callback = self.callback_cell.get(); - let raw_result = unsafe { - callback( - handle, - method, - args.data_pointer(), - args.len() as i32, - &mut ret_rbuf, - ) - }; - let result = CallbackResult::try_from(raw_result) - .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}")); - match result { - CallbackResult::Success => R::lift_callback_return(ret_rbuf), - CallbackResult::Error => R::lift_callback_error(ret_rbuf), - CallbackResult::UnexpectedError => { - let reason = if !ret_rbuf.is_empty() { - match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) { - Ok(s) => s, - Err(e) => { - log::error!("{{ trait_name }} Error reading ret_buf: {e}"); - String::from("[Error reading reason]") - } - } - } else { - RustBuffer::destroy(ret_rbuf); - String::from("[Unknown Reason]") - }; - R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason }) - } - } - } -} - /// Used when internal/unexpected error happened when calling a foreign callback, for example when /// a unknown exception is raised /// @@ -216,8 +115,10 @@ pub struct UnexpectedUniFFICallbackError { } impl UnexpectedUniFFICallbackError { - pub fn from_reason(reason: String) -> Self { - Self { reason } + pub fn new(reason: impl fmt::Display) -> Self { + Self { + reason: reason.to_string(), + } } } diff --git a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs index 1f86f6b13b..a992ab7384 100644 --- a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs +++ b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs @@ -39,6 +39,12 @@ impl FfiDefault for () { fn ffi_default() {} } +impl FfiDefault for crate::Handle { + fn ffi_default() -> Self { + Self::default() + } +} + impl FfiDefault for *const std::ffi::c_void { fn ffi_default() -> Self { std::ptr::null() @@ -51,9 +57,10 @@ impl FfiDefault for crate::RustBuffer { } } -impl FfiDefault for crate::ForeignExecutorHandle { +impl FfiDefault for crate::ForeignFuture { fn ffi_default() -> Self { - Self(std::ptr::null()) + extern "C" fn free(_handle: u64) {} + crate::ForeignFuture { handle: 0, free } } } diff --git a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs index ac2463cd8e..326ff12747 100644 --- a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs +++ b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs @@ -8,96 +8,32 @@ //! code loads the exported library. For each callback type, we also define a "cell" type for //! storing the callback. -use std::sync::atomic::{AtomicUsize, Ordering}; - -use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback}; - -/// ForeignCallback is the Rust representation of a foreign language function. -/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface, -/// at library start up time. -/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language. -/// -/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object -/// that implements the callback interface/trait. -/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from -/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all -/// bindings, so we can rely on the method list being stable within the same run of UniFFI. -/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code -/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the -/// arguments from the buffer and passes them to the user's callback. -/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a -/// buffer to write the result into. -/// * Callbacks return one of the `CallbackResult` values -/// Note: The output buffer might still contain 0 bytes of data. -pub type ForeignCallback = unsafe extern "C" fn( - handle: u64, - method: u32, - args_data: *const u8, - args_len: i32, - buf_ptr: *mut RustBuffer, -) -> i32; - -/// Callback to schedule a Rust call with a `ForeignExecutor`. The bindings code registers exactly -/// one of these with the Rust code. -/// -/// Delay is an approximate amount of ms to wait before scheduling the call. Delay is usually 0, -/// which means schedule sometime soon. -/// -/// As a special case, when Rust drops the foreign executor, with `task=null`. The foreign -/// bindings should release the reference to the executor that was reserved for Rust. -/// -/// This callback can be invoked from any thread, including threads created by Rust. -/// -/// The callback should return one of the `ForeignExecutorCallbackResult` values. -pub type ForeignExecutorCallback = extern "C" fn( - executor: ForeignExecutorHandle, - delay: u32, - task: Option<RustTaskCallback>, - task_data: *const (), -) -> i8; - -/// Store a [ForeignCallback] pointer -pub(crate) struct ForeignCallbackCell(AtomicUsize); - -/// Store a [ForeignExecutorCallback] pointer -pub(crate) struct ForeignExecutorCallbackCell(AtomicUsize); - -/// Macro to define foreign callback types as well as the callback cell. -macro_rules! impl_foreign_callback_cell { - ($callback_type:ident, $cell_type:ident) => { - // Overly-paranoid sanity checking to ensure that these types are - // convertible between each-other. `transmute` actually should check this for - // us too, but this helps document the invariants we rely on in this code. - // - // Note that these are guaranteed by - // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html - // and thus this is a little paranoid. - static_assertions::assert_eq_size!(usize, $callback_type); - static_assertions::assert_eq_size!(usize, Option<$callback_type>); - - impl $cell_type { - pub const fn new() -> Self { - Self(AtomicUsize::new(0)) - } - - pub fn set(&self, callback: $callback_type) { - // Store the pointer using Ordering::Relaxed. This is sufficient since callback - // should be set at startup, before there's any chance of using them. - self.0.store(callback as usize, Ordering::Relaxed); - } - - pub fn get(&self) -> $callback_type { - let ptr_value = self.0.load(Ordering::Relaxed); - unsafe { - // SAFETY: self.0 was set in `set` from our function pointer type, so - // it's safe to transmute it back here. - ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value) - .expect("Bug: callback not set. This is likely a uniffi bug.") - } - } +use std::{ + ptr::{null_mut, NonNull}, + sync::atomic::{AtomicPtr, Ordering}, +}; + +// Cell type that stores any NonNull<T> +#[doc(hidden)] +pub struct UniffiForeignPointerCell<T>(AtomicPtr<T>); + +impl<T> UniffiForeignPointerCell<T> { + pub const fn new() -> Self { + Self(AtomicPtr::new(null_mut())) + } + + pub fn set(&self, callback: NonNull<T>) { + self.0.store(callback.as_ptr(), Ordering::Relaxed); + } + + pub fn get(&self) -> &T { + unsafe { + NonNull::new(self.0.load(Ordering::Relaxed)) + .expect("Foreign pointer not set. This is likely a uniffi bug.") + .as_mut() } - }; + } } -impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell); -impl_foreign_callback_cell!(ForeignExecutorCallback, ForeignExecutorCallbackCell); +unsafe impl<T> Send for UniffiForeignPointerCell<T> {} +unsafe impl<T> Sync for UniffiForeignPointerCell<T> {} diff --git a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs deleted file mode 100644 index 7b1cb9bd80..0000000000 --- a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs +++ /dev/null @@ -1,487 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -//! Schedule tasks using a foreign executor. - -use std::panic; - -use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell}; - -/// Opaque handle for a foreign task executor. -/// -/// Foreign code can either use an actual pointer, or use an integer value casted to it. -#[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct ForeignExecutorHandle(pub(crate) *const ()); - -// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for -// making the `ForeignExecutorCallback` thread-safe. -unsafe impl Send for ForeignExecutorHandle {} - -unsafe impl Sync for ForeignExecutorHandle {} - -/// Result code returned by `ForeignExecutorCallback` -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum ForeignExecutorCallbackResult { - /// Callback was scheduled successfully - Success = 0, - /// Callback couldn't be scheduled because the foreign executor is canceled/closed. - Cancelled = 1, - /// Callback couldn't be scheduled because of some other error - Error = 2, -} - -impl ForeignExecutorCallbackResult { - /// Check the result code for the foreign executor callback - /// - /// If the result was `ForeignExecutorCallbackResult.Success`, this method returns `true`. - /// - /// If not, this method returns `false`, logging errors for any unexpected return values - pub fn check_result_code(result: i8) -> bool { - match result { - n if n == ForeignExecutorCallbackResult::Success as i8 => true, - n if n == ForeignExecutorCallbackResult::Cancelled as i8 => false, - n if n == ForeignExecutorCallbackResult::Error as i8 => { - log::error!( - "ForeignExecutorCallbackResult::Error returned by foreign executor callback" - ); - false - } - n => { - log::error!("Unknown code ({n}) returned by foreign executor callback"); - false - } - } - } -} - -// Option<RustTaskCallback> should use the null pointer optimization and be represented in C as a -// regular pointer. Let's check that. -static_assertions::assert_eq_size!(usize, Option<RustTaskCallback>); - -/// Callback for a Rust task, this is what the foreign executor invokes -/// -/// The task will be passed the `task_data` passed to `ForeignExecutorCallback` in addition to one -/// of the `RustTaskCallbackCode` values. -pub type RustTaskCallback = extern "C" fn(*const (), RustTaskCallbackCode); - -/// Passed to a `RustTaskCallback` function when the executor invokes them. -/// -/// Every `RustTaskCallback` will be invoked eventually, this code is used to distinguish the times -/// when it's invoked successfully vs times when the callback is being called because the foreign -/// executor has been cancelled / shutdown -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum RustTaskCallbackCode { - /// Successful task callback invocation - Success = 0, - /// The `ForeignExecutor` has been cancelled. - /// - /// This signals that any progress using the executor should be halted. In particular, Futures - /// should not continue to progress. - Cancelled = 1, -} - -static FOREIGN_EXECUTOR_CALLBACK: ForeignExecutorCallbackCell = ForeignExecutorCallbackCell::new(); - -/// Set the global ForeignExecutorCallback. This is called by the foreign bindings, normally -/// during initialization. -pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) { - FOREIGN_EXECUTOR_CALLBACK.set(callback); -} - -/// Schedule Rust calls using a foreign executor -#[derive(Debug)] -pub struct ForeignExecutor { - pub(crate) handle: ForeignExecutorHandle, -} - -impl ForeignExecutor { - pub fn new(executor: ForeignExecutorHandle) -> Self { - Self { handle: executor } - } - - /// Schedule a closure to be run. - /// - /// This method can be used for "fire-and-forget" style calls, where the calling code doesn't - /// need to await the result. - /// - /// Closure requirements: - /// - Send: since the closure will likely run on a different thread - /// - 'static: since it runs at an arbitrary time, so all references need to be 'static - /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data - pub fn schedule<F: FnOnce() + Send + 'static + panic::UnwindSafe>(&self, delay: u32, task: F) { - let leaked_ptr: *mut F = Box::leak(Box::new(task)); - if !schedule_raw( - self.handle, - delay, - schedule_callback::<F>, - leaked_ptr as *const (), - ) { - // If schedule_raw() failed, drop the leaked box since `schedule_callback()` has not been - // scheduled to run. - unsafe { - drop(Box::<F>::from_raw(leaked_ptr)); - }; - } - } - - /// Schedule a closure to be run and get a Future for the result - /// - /// Closure requirements: - /// - Send: since the closure will likely run on a different thread - /// - 'static: since it runs at an arbitrary time, so all references need to be 'static - /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data - pub async fn run<F, T>(&self, delay: u32, closure: F) -> T - where - F: FnOnce() -> T + Send + 'static + panic::UnwindSafe, - T: Send + 'static, - { - // Create a oneshot channel to handle the future - let (sender, receiver) = oneshot::channel(); - // We can use `AssertUnwindSafe` here because: - // - The closure is unwind safe - // - `Sender` is not marked unwind safe, maybe this is just an oversight in the oneshot - // library. However, calling `send()` and dropping the Sender should certainly be - // unwind safe. `send()` should probably not panic at all and if it does it shouldn't - // do it in a way that breaks the Receiver. - // - Calling `expect` may result in a panic, but this should should not break either the - // Sender or Receiver. - self.schedule( - delay, - panic::AssertUnwindSafe(move || { - sender.send(closure()).expect("Error sending future result") - }), - ); - receiver.await.expect("Error receiving future result") - } -} - -/// Low-level schedule interface -/// -/// When using this function, take care to ensure that the `ForeignExecutor` that holds the -/// `ForeignExecutorHandle` has not been dropped. -/// -/// Returns true if the callback was successfully scheduled -pub(crate) fn schedule_raw( - handle: ForeignExecutorHandle, - delay: u32, - callback: RustTaskCallback, - data: *const (), -) -> bool { - let result_code = (FOREIGN_EXECUTOR_CALLBACK.get())(handle, delay, Some(callback), data); - ForeignExecutorCallbackResult::check_result_code(result_code) -} - -impl Drop for ForeignExecutor { - fn drop(&mut self) { - (FOREIGN_EXECUTOR_CALLBACK.get())(self.handle, 0, None, std::ptr::null()); - } -} - -extern "C" fn schedule_callback<F>(data: *const (), status_code: RustTaskCallbackCode) -where - F: FnOnce() + Send + 'static + panic::UnwindSafe, -{ - // No matter what, we need to call Box::from_raw() to balance the Box::leak() call. - let task = unsafe { Box::from_raw(data as *mut F) }; - // Skip running the task for the `RustTaskCallbackCode::Cancelled` code - if status_code == RustTaskCallbackCode::Success { - run_task(task); - } -} - -/// Run a scheduled task, catching any panics. -/// -/// If there are panics, then we will log a warning and return None. -fn run_task<F: FnOnce() -> T + panic::UnwindSafe, T>(task: F) -> Option<T> { - match panic::catch_unwind(task) { - Ok(v) => Some(v), - Err(cause) => { - let message = if let Some(s) = cause.downcast_ref::<&'static str>() { - (*s).to_string() - } else if let Some(s) = cause.downcast_ref::<String>() { - s.clone() - } else { - "Unknown panic!".to_string() - }; - log::warn!("Error calling UniFFI callback function: {message}"); - None - } - } -} - -#[cfg(test)] -pub use test::MockEventLoop; - -#[cfg(test)] -mod test { - use super::*; - use std::{ - future::Future, - pin::Pin, - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, Mutex, Once, - }, - task::{Context, Poll, Wake, Waker}, - }; - - /// Simulate an event loop / task queue / coroutine scope on the foreign side - /// - /// This simply collects scheduled calls into a Vec for testing purposes. - /// - /// Most of the MockEventLoop methods are `pub` since it's also used by the `rustfuture` tests. - pub struct MockEventLoop { - // Wrap everything in a mutex since we typically share access to MockEventLoop via an Arc - inner: Mutex<MockEventLoopInner>, - } - - pub struct MockEventLoopInner { - // calls that have been scheduled - calls: Vec<(u32, Option<RustTaskCallback>, *const ())>, - // has the event loop been shutdown? - is_shutdown: bool, - } - - unsafe impl Send for MockEventLoopInner {} - - static FOREIGN_EXECUTOR_CALLBACK_INIT: Once = Once::new(); - - impl MockEventLoop { - pub fn new() -> Arc<Self> { - // Make sure we install a foreign executor callback that can deal with mock event loops - FOREIGN_EXECUTOR_CALLBACK_INIT - .call_once(|| foreign_executor_callback_set(mock_executor_callback)); - - Arc::new(Self { - inner: Mutex::new(MockEventLoopInner { - calls: vec![], - is_shutdown: false, - }), - }) - } - - /// Create a new ForeignExecutorHandle - pub fn new_handle(self: &Arc<Self>) -> ForeignExecutorHandle { - // To keep the memory management simple, we simply leak an arc reference for this. We - // only create a handful of these in the tests so there's no need for proper cleanup. - ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ()) - } - - pub fn new_executor(self: &Arc<Self>) -> ForeignExecutor { - ForeignExecutor { - handle: self.new_handle(), - } - } - - /// Get the current number of scheduled calls - pub fn call_count(&self) -> usize { - self.inner.lock().unwrap().calls.len() - } - - /// Get the last scheduled call - pub fn last_call(&self) -> (u32, Option<RustTaskCallback>, *const ()) { - self.inner - .lock() - .unwrap() - .calls - .last() - .cloned() - .expect("no calls scheduled") - } - - /// Run all currently scheduled calls - pub fn run_all_calls(&self) { - let mut inner = self.inner.lock().unwrap(); - let is_shutdown = inner.is_shutdown; - for (_delay, callback, data) in inner.calls.drain(..) { - if !is_shutdown { - callback.unwrap()(data, RustTaskCallbackCode::Success); - } else { - callback.unwrap()(data, RustTaskCallbackCode::Cancelled); - } - } - } - - /// Shutdown the eventloop, causing scheduled calls and future calls to be cancelled - pub fn shutdown(&self) { - self.inner.lock().unwrap().is_shutdown = true; - } - } - - // `ForeignExecutorCallback` that we install for testing - extern "C" fn mock_executor_callback( - handle: ForeignExecutorHandle, - delay: u32, - task: Option<RustTaskCallback>, - task_data: *const (), - ) -> i8 { - let eventloop = handle.0 as *const MockEventLoop; - let mut inner = unsafe { (*eventloop).inner.lock().unwrap() }; - if inner.is_shutdown { - ForeignExecutorCallbackResult::Cancelled as i8 - } else { - inner.calls.push((delay, task, task_data)); - ForeignExecutorCallbackResult::Success as i8 - } - } - - #[test] - fn test_schedule_raw() { - extern "C" fn callback(data: *const (), _status_code: RustTaskCallbackCode) { - unsafe { - *(data as *mut u32) += 1; - } - } - - let eventloop = MockEventLoop::new(); - - let value: u32 = 0; - assert_eq!(eventloop.call_count(), 0); - - schedule_raw( - eventloop.new_handle(), - 0, - callback, - &value as *const u32 as *const (), - ); - assert_eq!(eventloop.call_count(), 1); - assert_eq!(value, 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(value, 1); - } - - #[test] - fn test_schedule() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - let value = Arc::new(AtomicU32::new(0)); - assert_eq!(eventloop.call_count(), 0); - - let value2 = value.clone(); - executor.schedule(0, move || { - value2.fetch_add(1, Ordering::Relaxed); - }); - assert_eq!(eventloop.call_count(), 1); - assert_eq!(value.load(Ordering::Relaxed), 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(value.load(Ordering::Relaxed), 1); - } - - #[derive(Default)] - struct MockWaker { - wake_count: AtomicU32, - } - - impl Wake for MockWaker { - fn wake(self: Arc<Self>) { - self.wake_count.fetch_add(1, Ordering::Relaxed); - } - } - - #[test] - fn test_run() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - let mock_waker = Arc::new(MockWaker::default()); - let waker = Waker::from(mock_waker.clone()); - let mut context = Context::from_waker(&waker); - assert_eq!(eventloop.call_count(), 0); - - let mut future = executor.run(0, move || "test-return-value"); - unsafe { - assert_eq!( - Pin::new_unchecked(&mut future).poll(&mut context), - Poll::Pending - ); - } - assert_eq!(eventloop.call_count(), 1); - assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 1); - unsafe { - assert_eq!( - Pin::new_unchecked(&mut future).poll(&mut context), - Poll::Ready("test-return-value") - ); - } - } - - #[test] - fn test_drop() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - - drop(executor); - // Calling drop should schedule a call with null task data. - assert_eq!(eventloop.call_count(), 1); - assert_eq!(eventloop.last_call().1, None); - } - - // Test that cancelled calls never run - #[test] - fn test_cancelled_call() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - // Create a shared counter - let counter = Arc::new(AtomicU32::new(0)); - // schedule increments using both `schedule()` and run()` - let counter_clone = Arc::clone(&counter); - executor.schedule(0, move || { - counter_clone.fetch_add(1, Ordering::Relaxed); - }); - let counter_clone = Arc::clone(&counter); - let future = executor.run(0, move || { - counter_clone.fetch_add(1, Ordering::Relaxed); - }); - // shutdown the eventloop before the scheduled call gets a chance to run. - eventloop.shutdown(); - // `run_all_calls()` will cause the scheduled task callbacks to run, but will pass - // `RustTaskCallbackCode::Cancelled` to it. This drop the scheduled closure without executing - // it. - eventloop.run_all_calls(); - - assert_eq!(counter.load(Ordering::Relaxed), 0); - drop(future); - } - - // Test that when scheduled calls are cancelled, the closures are dropped properly - #[test] - fn test_cancellation_drops_closures() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - - // Create an Arc<> that we will move into the closures to test if they are dropped or not - let arc = Arc::new(0); - let arc_clone = Arc::clone(&arc); - executor.schedule(0, move || assert_eq!(*arc_clone, 0)); - let arc_clone = Arc::clone(&arc); - let future = executor.run(0, move || assert_eq!(*arc_clone, 0)); - - // shutdown the eventloop and run the (cancelled) scheduled calls. - eventloop.shutdown(); - eventloop.run_all_calls(); - // try to schedule some more calls now that the loop has been shutdown - let arc_clone = Arc::clone(&arc); - executor.schedule(0, move || assert_eq!(*arc_clone, 0)); - let arc_clone = Arc::clone(&arc); - let future2 = executor.run(0, move || assert_eq!(*arc_clone, 0)); - - // Drop the futures so they don't hold on to any references - drop(future); - drop(future2); - - // All of these closures should have been dropped by now, there only remaining arc - // reference should be the original - assert_eq!(Arc::strong_count(&arc), 1); - } -} diff --git a/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs new file mode 100644 index 0000000000..be6a214e84 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! This module defines a Rust Future that wraps an async foreign function call. +//! +//! The general idea is to create a [oneshot::Channel], hand the sender to the foreign side, and +//! await the receiver side on the Rust side. +//! +//! The foreign side should: +//! * Input a [ForeignFutureCallback] and a `u64` handle in their scaffolding function. +//! This is the sender, converted to a raw pointer, and an extern "C" function that sends the result. +//! * Return a [ForeignFuture], which represents the foreign task object corresponding to the async function. +//! * Call the [ForeignFutureCallback] when the async function completes with: +//! * The `u64` handle initially passed in +//! * The `ForeignFutureResult` for the call +//! * Wait for the [ForeignFutureHandle::free] function to be called to free the task object. +//! If this is called before the task completes, then the task will be cancelled. + +use crate::{LiftReturn, RustCallStatus, UnexpectedUniFFICallbackError}; + +/// Handle for a foreign future +pub type ForeignFutureHandle = u64; + +/// Handle for a callback data associated with a foreign future. +pub type ForeignFutureCallbackData = *mut (); + +/// Callback that's passed to a foreign async functions. +/// +/// See `LiftReturn` trait for how this is implemented. +pub type ForeignFutureCallback<FfiType> = + extern "C" fn(oneshot_handle: u64, ForeignFutureResult<FfiType>); + +/// C struct that represents the result of a foreign future +#[repr(C)] +pub struct ForeignFutureResult<T> { + // Note: for void returns, T is `()`, which isn't directly representable with C since it's a ZST. + // Foreign code should treat that case as if there was no `return_value` field. + return_value: T, + call_status: RustCallStatus, +} + +/// Perform a call to a foreign async method + +/// C struct that represents the foreign future. +/// +/// This is what's returned by the async scaffolding functions. +#[repr(C)] +pub struct ForeignFuture { + pub handle: ForeignFutureHandle, + pub free: extern "C" fn(handle: ForeignFutureHandle), +} + +impl Drop for ForeignFuture { + fn drop(&mut self) { + (self.free)(self.handle) + } +} + +unsafe impl Send for ForeignFuture {} + +pub async fn foreign_async_call<F, T, UT>(call_scaffolding_function: F) -> T +where + F: FnOnce(ForeignFutureCallback<T::ReturnType>, u64) -> ForeignFuture, + T: LiftReturn<UT>, +{ + let (sender, receiver) = oneshot::channel::<ForeignFutureResult<T::ReturnType>>(); + // Keep the ForeignFuture around, even though we don't ever use it. + // The important thing is that the ForeignFuture will be dropped when this Future is. + let _foreign_future = + call_scaffolding_function(foreign_future_complete::<T, UT>, sender.into_raw() as u64); + match receiver.await { + Ok(result) => T::lift_foreign_return(result.return_value, result.call_status), + Err(e) => { + // This shouldn't happen in practice, but we can do our best to recover + T::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(format!( + "Error awaiting foreign future: {e}" + ))) + } + } +} + +pub extern "C" fn foreign_future_complete<T: LiftReturn<UT>, UT>( + oneshot_handle: u64, + result: ForeignFutureResult<T::ReturnType>, +) { + let channel = unsafe { oneshot::Sender::from_raw(oneshot_handle as *mut ()) }; + // Ignore errors in send. + // + // Error means the receiver was already dropped which will happen when the future is cancelled. + let _ = channel.send(result); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{Lower, RustBuffer}; + use once_cell::sync::OnceCell; + use std::{ + future::Future, + pin::Pin, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, + task::{Context, Poll, Wake}, + }; + + struct MockForeignFuture { + freed: Arc<AtomicU32>, + callback_info: Arc<OnceCell<(ForeignFutureCallback<RustBuffer>, u64)>>, + rust_future: Option<Pin<Box<dyn Future<Output = String>>>>, + } + + impl MockForeignFuture { + fn new() -> Self { + let callback_info = Arc::new(OnceCell::new()); + let freed = Arc::new(AtomicU32::new(0)); + + let rust_future: Pin<Box<dyn Future<Output = String>>> = { + let callback_info = callback_info.clone(); + let freed = freed.clone(); + Box::pin(foreign_async_call::<_, String, crate::UniFfiTag>( + move |callback, data| { + callback_info.set((callback, data)).unwrap(); + ForeignFuture { + handle: Arc::into_raw(freed) as *mut () as u64, + free: Self::free, + } + }, + )) + }; + let rust_future = Some(rust_future); + let mut mock_foreign_future = Self { + freed, + callback_info, + rust_future, + }; + // Poll the future once, to start it up. This ensures that `callback_info` is set. + let _ = mock_foreign_future.poll(); + mock_foreign_future + } + + fn poll(&mut self) -> Poll<String> { + let waker = Arc::new(NoopWaker).into(); + let mut context = Context::from_waker(&waker); + self.rust_future + .as_mut() + .unwrap() + .as_mut() + .poll(&mut context) + } + + fn complete_success(&self, value: String) { + let (callback, data) = self.callback_info.get().unwrap(); + callback( + *data, + ForeignFutureResult { + return_value: <String as Lower<crate::UniFfiTag>>::lower(value), + call_status: RustCallStatus::new(), + }, + ); + } + + fn complete_error(&self, error_message: String) { + let (callback, data) = self.callback_info.get().unwrap(); + callback( + *data, + ForeignFutureResult { + return_value: RustBuffer::default(), + call_status: RustCallStatus::error(error_message), + }, + ); + } + + fn drop_future(&mut self) { + self.rust_future = None + } + + fn free_count(&self) -> u32 { + self.freed.load(Ordering::Relaxed) + } + + extern "C" fn free(handle: u64) { + let flag = unsafe { Arc::from_raw(handle as *mut AtomicU32) }; + flag.fetch_add(1, Ordering::Relaxed); + } + } + + struct NoopWaker; + + impl Wake for NoopWaker { + fn wake(self: Arc<Self>) {} + } + + #[test] + fn test_foreign_future() { + let mut mock_foreign_future = MockForeignFuture::new(); + assert_eq!(mock_foreign_future.poll(), Poll::Pending); + mock_foreign_future.complete_success("It worked!".to_owned()); + assert_eq!( + mock_foreign_future.poll(), + Poll::Ready("It worked!".to_owned()) + ); + // Since the future is complete, it should free the foreign future + assert_eq!(mock_foreign_future.free_count(), 1); + } + + #[test] + #[should_panic] + fn test_foreign_future_error() { + let mut mock_foreign_future = MockForeignFuture::new(); + assert_eq!(mock_foreign_future.poll(), Poll::Pending); + mock_foreign_future.complete_error("It Failed!".to_owned()); + let _ = mock_foreign_future.poll(); + } + + #[test] + fn test_drop_after_complete() { + let mut mock_foreign_future = MockForeignFuture::new(); + mock_foreign_future.complete_success("It worked!".to_owned()); + assert_eq!(mock_foreign_future.free_count(), 0); + assert_eq!( + mock_foreign_future.poll(), + Poll::Ready("It worked!".to_owned()) + ); + // Dropping the future after it's complete should not panic, and not cause a double-free + mock_foreign_future.drop_future(); + assert_eq!(mock_foreign_future.free_count(), 1); + } + + #[test] + fn test_drop_before_complete() { + let mut mock_foreign_future = MockForeignFuture::new(); + mock_foreign_future.complete_success("It worked!".to_owned()); + // Dropping the future before it's complete should cancel the future + assert_eq!(mock_foreign_future.free_count(), 0); + mock_foreign_future.drop_future(); + assert_eq!(mock_foreign_future.free_count(), 1); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/handle.rs b/third_party/rust/uniffi_core/src/ffi/handle.rs new file mode 100644 index 0000000000..8ee2f46c35 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/handle.rs @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Object handle +/// +/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in +/// Rust and ones implemented in the foreign language. +/// +/// Rust handles are generated by leaking a raw pointer +/// Foreign handles are generated with a handle map that only generates odd values. +/// For all currently supported architectures and hopefully any ones we add in the future: +/// * 0 is an invalid value. +/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the +/// leaked pointer will be aligned). +/// +/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Handle(u64); + +impl Handle { + pub fn from_pointer<T>(ptr: *const T) -> Self { + Self(ptr as u64) + } + + pub fn as_pointer<T>(&self) -> *const T { + self.0 as *const T + } + + pub fn from_raw(raw: u64) -> Option<Self> { + if raw == 0 { + None + } else { + Some(Self(raw)) + } + } + + pub fn from_raw_unchecked(raw: u64) -> Self { + Self(raw) + } + + pub fn as_raw(&self) -> u64 { + self.0 + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/mod.rs b/third_party/rust/uniffi_core/src/ffi/mod.rs index b606323297..acaf2b0d06 100644 --- a/third_party/rust/uniffi_core/src/ffi/mod.rs +++ b/third_party/rust/uniffi_core/src/ffi/mod.rs @@ -8,7 +8,8 @@ pub mod callbackinterface; pub mod ffidefault; pub mod foreignbytes; pub mod foreigncallbacks; -pub mod foreignexecutor; +pub mod foreignfuture; +pub mod handle; pub mod rustbuffer; pub mod rustcalls; pub mod rustfuture; @@ -17,7 +18,8 @@ pub use callbackinterface::*; pub use ffidefault::FfiDefault; pub use foreignbytes::*; pub use foreigncallbacks::*; -pub use foreignexecutor::*; +pub use foreignfuture::*; +pub use handle::*; pub use rustbuffer::*; pub use rustcalls::*; pub use rustfuture::*; diff --git a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs index e09e3be89a..8b2972968c 100644 --- a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs +++ b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs @@ -52,11 +52,11 @@ use crate::ffi::{rust_call, ForeignBytes, RustCallStatus}; #[derive(Debug)] pub struct RustBuffer { /// The allocated capacity of the underlying `Vec<u8>`. - /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. - capacity: i32, + /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple. + capacity: u64, /// The occupied length of the underlying `Vec<u8>`. - /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA. - len: i32, + /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple. + len: u64, /// The pointer to the allocated buffer of the `Vec<u8>`. data: *mut u8, } @@ -84,7 +84,7 @@ impl RustBuffer { /// # Safety /// /// You must ensure that the raw parts uphold the documented invariants of this class. - pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self { + pub unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self { Self { capacity, len, @@ -126,12 +126,8 @@ impl RustBuffer { /// /// Panics if the requested size is too large to fit in an `i32`, and /// hence would risk incompatibility with some foreign-language code. - pub fn new_with_size(size: usize) -> Self { - assert!( - size < i32::MAX as usize, - "RustBuffer requested size too large" - ); - Self::from_vec(vec![0u8; size]) + pub fn new_with_size(size: u64) -> Self { + Self::from_vec(vec![0u8; size as usize]) } /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`. @@ -144,8 +140,8 @@ impl RustBuffer { /// Panics if the vector's length or capacity are too large to fit in an `i32`, /// and hence would risk incompatibility with some foreign-language code. pub fn from_vec(v: Vec<u8>) -> Self { - let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32."); - let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32."); + let capacity = u64::try_from(v.capacity()).expect("buffer capacity cannot fit into a u64."); + let len = u64::try_from(v.len()).expect("buffer length cannot fit into a u64."); let mut v = std::mem::ManuallyDrop::new(v); unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) } } @@ -198,39 +194,18 @@ impl Default for RustBuffer { } } -// extern "C" functions for the RustBuffer functionality. +// Functions for the RustBuffer functionality. // -// These are used in two ways: -// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer -// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings. -// -// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL -// hash This creates a separate set of functions for each UniFFIed component, which is needed -// in the case where we create multiple dylib artifacts since each dylib will have its own -// allocator. +// The scaffolding code re-exports these functions, prefixed with the component name and UDL hash +// This creates a separate set of functions for each UniFFIed component, which is needed in the +// case where we create multiple dylib artifacts since each dylib will have its own allocator. /// This helper allocates a new byte buffer owned by the Rust code, and returns it /// to the foreign-language code as a `RustBuffer` struct. Callers must eventually /// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined /// below, or by passing ownership of the buffer back into Rust code. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_alloc( - size: i32, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_alloc(size, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] -pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { - _uniffi_rustbuffer_alloc(size, call_status) -} - -fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer { - rust_call(call_status, || { - Ok(RustBuffer::new_with_size(size.max(0) as usize)) - }) +pub fn uniffi_rustbuffer_alloc(size: u64, call_status: &mut RustCallStatus) -> RustBuffer { + rust_call(call_status, || Ok(RustBuffer::new_with_size(size))) } /// This helper copies bytes owned by the foreign-language code into a new byte buffer owned @@ -241,27 +216,10 @@ fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> Rust /// # Safety /// This function will dereference a provided pointer in order to copy bytes from it, so /// make sure the `ForeignBytes` struct contains a valid pointer and length. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_from_bytes( - bytes: ForeignBytes, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_from_bytes(bytes, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] pub fn uniffi_rustbuffer_from_bytes( bytes: ForeignBytes, call_status: &mut RustCallStatus, ) -> RustBuffer { - _uniffi_rustbuffer_from_bytes(bytes, call_status) -} - -fn _uniffi_rustbuffer_from_bytes( - bytes: ForeignBytes, - call_status: &mut RustCallStatus, -) -> RustBuffer { rust_call(call_status, || { let bytes = bytes.as_slice(); Ok(RustBuffer::from_vec(bytes.to_vec())) @@ -274,18 +232,7 @@ fn _uniffi_rustbuffer_from_bytes( /// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call /// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or /// corrupting the allocator state. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { - _uniffi_rustbuffer_free(buf, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { - _uniffi_rustbuffer_free(buf, call_status) -} - -fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { rust_call(call_status, || { RustBuffer::destroy(buf); Ok(()) @@ -307,28 +254,9 @@ fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) { /// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call /// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or /// corrupting the allocator state. -#[cfg(feature = "extern-rustbuffer")] -#[no_mangle] -pub extern "C" fn uniffi_rustbuffer_reserve( - buf: RustBuffer, - additional: i32, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_reserve(buf, additional, call_status) -} - -#[cfg(not(feature = "extern-rustbuffer"))] pub fn uniffi_rustbuffer_reserve( buf: RustBuffer, - additional: i32, - call_status: &mut RustCallStatus, -) -> RustBuffer { - _uniffi_rustbuffer_reserve(buf, additional, call_status) -} - -fn _uniffi_rustbuffer_reserve( - buf: RustBuffer, - additional: i32, + additional: u64, call_status: &mut RustCallStatus, ) -> RustBuffer { rust_call(call_status, || { @@ -394,24 +322,6 @@ mod test { #[test] #[should_panic] - fn test_rustbuffer_provided_capacity_must_be_non_negative() { - // We guard against foreign-language code providing this kind of invalid struct. - let mut v = vec![0u8, 1, 2]; - let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) }; - rbuf.destroy_into_vec(); - } - - #[test] - #[should_panic] - fn test_rustbuffer_provided_len_must_be_non_negative() { - // We guard against foreign-language code providing this kind of invalid struct. - let mut v = vec![0u8, 1, 2]; - let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) }; - rbuf.destroy_into_vec(); - } - - #[test] - #[should_panic] fn test_rustbuffer_provided_len_must_not_exceed_capacity() { // We guard against foreign-language code providing this kind of invalid struct. let mut v = vec![0u8, 1, 2]; diff --git a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs index 53265393c0..16b0c76f2e 100644 --- a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs +++ b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs @@ -56,6 +56,13 @@ pub struct RustCallStatus { } impl RustCallStatus { + pub fn new() -> Self { + Self { + code: RustCallStatusCode::Success, + error_buf: MaybeUninit::new(RustBuffer::new()), + } + } + pub fn cancelled() -> Self { Self { code: RustCallStatusCode::Cancelled, @@ -102,7 +109,7 @@ pub enum RustCallStatusCode { /// Handle a scaffolding calls /// /// `callback` is responsible for making the actual Rust call and returning a special result type: -/// - For successfull calls, return `Ok(value)` +/// - For successful calls, return `Ok(value)` /// - For errors that should be translated into thrown exceptions in the foreign code, serialize /// the error into a `RustBuffer`, then return `Ok(buf)` /// - The success type, must implement `FfiDefault`. diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture.rs deleted file mode 100644 index 0c1a24174b..0000000000 --- a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs +++ /dev/null @@ -1,735 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. -//! -//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. -//! -//! # The big picture -//! -//! We implement async foreign functions using a simplified version of the Future API: -//! -//! 0. At startup, register a [RustFutureContinuationCallback] by calling -//! rust_future_continuation_callback_set. -//! 1. Call the scaffolding function to get a [RustFutureHandle] -//! 2a. In a loop: -//! - Call [rust_future_poll] -//! - Suspend the function until the [rust_future_poll] continuation function is called -//! - If the continuation was function was called with [RustFuturePoll::Ready], then break -//! otherwise continue. -//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the -//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to -//! enter a cancelled state. -//! 3. Call [rust_future_complete] to get the result of the future. -//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: -//! - Releases any resources held by the future -//! - Calls any continuation callbacks that have not been called yet -//! -//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` -//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", -//! and manually monomorphized in the case of [rust_future_complete]. See -//! `uniffi_macros/src/setup_scaffolding.rs` for details. -//! -//! ## How does `Future` work exactly? -//! -//! A [`Future`] in Rust does nothing. When calling an async function, it just -//! returns a `Future` but nothing has happened yet. To start the computation, -//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if -//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically -//! means: -//! -//! > Please, try to poll me later, maybe the result will be ready! -//! -//! This model is very different than what other languages do, but it can actually -//! be translated quite easily, fortunately for us! -//! -//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does -//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the -//! `Future`: that's where they are polled. -//! -//! But… wait another minute… how does the executor know when to poll a [`Future`]? -//! Does it poll them randomly in an endless loop? Well, no, actually it depends -//! on the executor! A well-designed `Future` and executor work as follows. -//! Normally, when [`Future::poll`] is called, a [`Context`] argument is -//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a -//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will -//! signal the executor to poll a particular `Future`. A `Future` will clone -//! or pass-by-ref the waker to somewhere, as a callback, a completion, a -//! function, or anything, to the system that is responsible to notify when a -//! task is completed. So, to recap, the waker is _not_ responsible for waking the -//! `Future`, it _is_ responsible for _signaling_ the executor that a particular -//! `Future` should be polled again. That's why the documentation of -//! [`Poll::Pending`] specifies: -//! -//! > When a function returns `Pending`, the function must also ensure that the -//! > current task is scheduled to be awoken when progress can be made. -//! -//! “awakening” is done by using the `Waker`. -//! -//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html -//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll -//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready -//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending -//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html -//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html -//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html - -use std::{ - future::Future, - marker::PhantomData, - mem, - ops::Deref, - panic, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll, Wake}, -}; - -use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; - -/// Result code for [rust_future_poll]. This is passed to the continuation function. -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum RustFuturePoll { - /// The future is ready and is waiting for [rust_future_complete] to be called - Ready = 0, - /// The future might be ready and [rust_future_poll] should be called again - MaybeReady = 1, -} - -/// Foreign callback that's passed to [rust_future_poll] -/// -/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again -/// to continue progress on the future. -pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); - -/// Opaque handle for a Rust future that's stored by the foreign language code -#[repr(transparent)] -pub struct RustFutureHandle(*const ()); - -// === Public FFI API === - -/// Create a new [RustFutureHandle] -/// -/// For each exported async function, UniFFI will create a scaffolding function that uses this to -/// create the [RustFutureHandle] to pass to the foreign code. -pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle -where - // F is the future type returned by the exported async function. It needs to be Send + `static - // since it will move between threads for an indeterminate amount of time as the foreign - // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, - // since we synchronize all access to the values. - F: Future<Output = T> + Send + 'static, - // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + - // 'static for the same reason as F. - T: LowerReturn<UT> + Send + 'static, - // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. - UT: Send + 'static, -{ - // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to - // implement the FFI - let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>; - // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it - // to the foreign code. - let boxed_ffi = Box::new(future_ffi); - // We can now create a RustFutureHandle - RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) -} - -/// Poll a Rust future -/// -/// When the future is ready to progress the continuation will be called with the `data` value and -/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called -/// exactly once. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll<ReturnType>( - handle: RustFutureHandle, - callback: RustFutureContinuationCallback, - data: *const (), -) { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.clone().ffi_poll(callback, data) -} - -/// Cancel a Rust future -/// -/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. -/// -/// This is needed for languages like Swift, which continuation to wait for the continuation to be -/// called when tasks are cancelled. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.clone().ffi_cancel() -} - -/// Complete a Rust future -/// -/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for -/// each supported FFI type. -/// -/// # Safety -/// -/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] -/// - The `T` param must correctly correspond to the [rust_future_new] call. It must -/// be `<Output as LowerReturn<UT>>::ReturnType` -pub unsafe fn rust_future_complete<ReturnType>( - handle: RustFutureHandle, - out_status: &mut RustCallStatus, -) -> ReturnType { - let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.ffi_complete(out_status) -} - -/// Free a Rust future, dropping the strong reference and releasing all references held by the -/// future. -/// -/// # Safety -/// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) { - let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>); - future.ffi_free() -} - -/// Thread-safe storage for [RustFutureContinuationCallback] data -/// -/// The basic guarantee is that all data pointers passed in are passed out exactly once to the -/// foreign continuation callback. This enables us to uphold the [rust_future_poll] guarantee. -/// -/// [ContinuationDataCell] also tracks cancellation, which is closely tied to continuation data. -#[derive(Debug)] -enum ContinuationDataCell { - /// No continuations set, neither wake() nor cancel() called. - Empty, - /// `wake()` was called when there was no continuation set. The next time `store` is called, - /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` - Waked, - /// The future has been cancelled, any future `store` calls should immediately result in the - /// continuation being called with `RustFuturePoll::Ready`. - Cancelled, - /// Continuation set, the next time `wake()` is called is called, we should invoke it. - Set(RustFutureContinuationCallback, *const ()), -} - -impl ContinuationDataCell { - fn new() -> Self { - Self::Empty - } - - /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or - /// `Cancelled` state, call the continuation immediately with the data. - fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) { - match self { - Self::Empty => *self = Self::Set(callback, data), - Self::Set(old_callback, old_data) => { - log::error!( - "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" - ); - old_callback(*old_data, RustFuturePoll::Ready); - *self = Self::Set(callback, data); - } - Self::Waked => { - *self = Self::Empty; - callback(data, RustFuturePoll::MaybeReady); - } - Self::Cancelled => { - callback(data, RustFuturePoll::Ready); - } - } - } - - fn wake(&mut self) { - match self { - // If we had a continuation set, then call it and transition to the `Empty` state. - Self::Set(callback, old_data) => { - let old_data = *old_data; - let callback = *callback; - *self = Self::Empty; - callback(old_data, RustFuturePoll::MaybeReady); - } - // If we were in the `Empty` state, then transition to `Waked`. The next time `store` - // is called, we will immediately call the continuation. - Self::Empty => *self = Self::Waked, - // This is a no-op if we were in the `Cancelled` or `Waked` state. - _ => (), - } - } - - fn cancel(&mut self) { - if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { - callback(old_data, RustFuturePoll::Ready); - } - } - - fn is_cancelled(&self) -> bool { - matches!(self, Self::Cancelled) - } -} - -// ContinuationDataCell is Send + Sync as long we handle the *const () pointer correctly - -unsafe impl Send for ContinuationDataCell {} -unsafe impl Sync for ContinuationDataCell {} - -/// Wraps the actual future we're polling -struct WrappedFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - // Note: this could be a single enum, but that would make it easy to mess up the future pinning - // guarantee. For example you might want to call `std::mem::take()` to try to get the result, - // but if the future happened to be stored that would move and break all internal references. - future: Option<F>, - result: Option<Result<T::ReturnType, RustCallStatus>>, -} - -impl<F, T, UT> WrappedFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn new(future: F) -> Self { - Self { - future: Some(future), - result: None, - } - } - - // Poll the future and check if it's ready or not - fn poll(&mut self, context: &mut Context<'_>) -> bool { - if self.result.is_some() { - true - } else if let Some(future) = &mut self.future { - // SAFETY: We can call Pin::new_unchecked because: - // - This is the only time we get a &mut to `self.future` - // - We never poll the future after it's moved (for example by using take()) - // - We never move RustFuture, which contains us. - // - RustFuture is private to this module so no other code can move it. - let pinned = unsafe { Pin::new_unchecked(future) }; - // Run the poll and lift the result if it's ready - let mut out_status = RustCallStatus::default(); - let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status( - &mut out_status, - // This closure uses a `&mut F` value, which means it's not UnwindSafe by - // default. If the future panics, it may be in an invalid state. - // - // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` - // case below and we will never poll the future again. - panic::AssertUnwindSafe(|| match pinned.poll(context) { - Poll::Pending => Ok(Poll::Pending), - Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), - }), - ); - match result { - Some(Poll::Pending) => false, - Some(Poll::Ready(v)) => { - self.future = None; - self.result = Some(Ok(v)); - true - } - None => { - self.future = None; - self.result = Some(Err(out_status)); - true - } - } - } else { - log::error!("poll with neither future nor result set"); - true - } - } - - fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { - let mut return_value = T::ReturnType::ffi_default(); - match self.result.take() { - Some(Ok(v)) => return_value = v, - Some(Err(call_status)) => *out_status = call_status, - None => *out_status = RustCallStatus::cancelled(), - } - self.free(); - return_value - } - - fn free(&mut self) { - self.future = None; - self.result = None; - } -} - -// If F and T are Send, then WrappedFuture is too -// -// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising -// that we will treat the raw pointer properly, for example by not returning it twice. -unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ -} - -/// Future that the foreign code is awaiting -struct RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - // This Mutex should never block if our code is working correctly, since there should not be - // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. - future: Mutex<WrappedFuture<F, T, UT>>, - continuation_data: Mutex<ContinuationDataCell>, - // UT is used as the generic parameter for [LowerReturn]. - // Let's model this with PhantomData as a function that inputs a UT value. - _phantom: PhantomData<fn(UT) -> ()>, -} - -impl<F, T, UT> RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn new(future: F, _tag: UT) -> Arc<Self> { - Arc::new(Self { - future: Mutex::new(WrappedFuture::new(future)), - continuation_data: Mutex::new(ContinuationDataCell::new()), - _phantom: PhantomData, - }) - } - - fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { - let ready = self.is_cancelled() || { - let mut locked = self.future.lock().unwrap(); - let waker: std::task::Waker = Arc::clone(&self).into(); - locked.poll(&mut Context::from_waker(&waker)) - }; - if ready { - callback(data, RustFuturePoll::Ready) - } else { - self.continuation_data.lock().unwrap().store(callback, data); - } - } - - fn is_cancelled(&self) -> bool { - self.continuation_data.lock().unwrap().is_cancelled() - } - - fn wake(&self) { - self.continuation_data.lock().unwrap().wake(); - } - - fn cancel(&self) { - self.continuation_data.lock().unwrap().cancel(); - } - - fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { - self.future.lock().unwrap().complete(call_status) - } - - fn free(self: Arc<Self>) { - // Call cancel() to send any leftover data to the continuation callback - self.continuation_data.lock().unwrap().cancel(); - // Ensure we drop our inner future, releasing all held references - self.future.lock().unwrap().free(); - } -} - -impl<F, T, UT> Wake for RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn wake(self: Arc<Self>) { - self.deref().wake() - } - - fn wake_by_ref(self: &Arc<Self>) { - self.deref().wake() - } -} - -/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to -/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this -/// allows you to use RustFuture functionality without knowing the concrete Future type, which is -/// unnamable. -/// -/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of -/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need -/// to create a poll, cancel, complete, and free scaffolding function for each exported async -/// function. That would add ~1kb binary size per exported function based on a quick estimate on a -/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and -/// only create those functions for each of the 13 possible FFI return types. -#[doc(hidden)] -trait RustFutureFfi<ReturnType> { - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()); - fn ffi_cancel(&self); - fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; - fn ffi_free(self: Arc<Self>); -} - -impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT> -where - // See rust_future_new for an explanation of these trait bounds - F: Future<Output = T> + Send + 'static, - T: LowerReturn<UT> + Send + 'static, - UT: Send + 'static, -{ - fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) { - self.poll(callback, data) - } - - fn ffi_cancel(&self) { - self.cancel() - } - - fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { - self.complete(call_status) - } - - fn ffi_free(self: Arc<Self>) { - self.free(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; - use once_cell::sync::OnceCell; - use std::task::Waker; - - // Sender/Receiver pair that we use for testing - struct Channel { - result: Option<Result<String, TestError>>, - waker: Option<Waker>, - } - - struct Sender(Arc<Mutex<Channel>>); - - impl Sender { - fn wake(&self) { - let inner = self.0.lock().unwrap(); - if let Some(waker) = &inner.waker { - waker.wake_by_ref(); - } - } - - fn send(&self, value: Result<String, TestError>) { - let mut inner = self.0.lock().unwrap(); - if inner.result.replace(value).is_some() { - panic!("value already sent"); - } - if let Some(waker) = &inner.waker { - waker.wake_by_ref(); - } - } - } - - struct Receiver(Arc<Mutex<Channel>>); - - impl Future for Receiver { - type Output = Result<String, TestError>; - - fn poll( - self: Pin<&mut Self>, - context: &mut Context<'_>, - ) -> Poll<Result<String, TestError>> { - let mut inner = self.0.lock().unwrap(); - match &inner.result { - Some(v) => Poll::Ready(v.clone()), - None => { - inner.waker = Some(context.waker().clone()); - Poll::Pending - } - } - } - } - - // Create a sender and rust future that we can use for testing - fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { - let channel = Arc::new(Mutex::new(Channel { - result: None, - waker: None, - })); - let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); - (Sender(channel), rust_future) - } - - /// Poll a Rust future and get an OnceCell that's set when the continuation is called - fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { - let cell = Arc::new(OnceCell::new()); - let cell_ptr = Arc::into_raw(cell.clone()) as *const (); - rust_future.clone().ffi_poll(poll_continuation, cell_ptr); - cell - } - - extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) { - let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; - cell.set(code).expect("Error setting OnceCell"); - } - - fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) { - let mut out_status_code = RustCallStatus::default(); - let return_value = rust_future.ffi_complete(&mut out_status_code); - (return_value, out_status_code) - } - - #[test] - fn test_success() { - let (sender, rust_future) = channel(); - - // Test polling the rust future before it's ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.wake(); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - // Test polling the rust future when it's ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.send(Ok("All done".into())); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - // Future polls should immediately return ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - // Complete the future - let (return_buf, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), - "All done" - ); - } - - #[test] - fn test_error() { - let (sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - sender.send(Err("Something went wrong".into())); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - let (_, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Error); - unsafe { - assert_eq!( - <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer( - call_status.error_buf.assume_init() - ) - .unwrap(), - TestError::from("Something went wrong"), - ) - } - } - - // Once `complete` is called, the inner future should be released, even if wakers still hold a - // reference to the RustFuture - #[test] - fn test_cancel() { - let (_sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), None); - rust_future.ffi_cancel(); - // Cancellation should immediately invoke the callback with RustFuturePoll::Ready - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - // Future polls should immediately invoke the callback with RustFuturePoll::Ready - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - - let (_, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Cancelled); - } - - // Once `free` is called, the inner future should be released, even if wakers still hold a - // reference to the RustFuture - #[test] - fn test_release_future() { - let (sender, rust_future) = channel(); - // Create a weak reference to the channel to use to check if rust_future has dropped its - // future. - let channel_weak = Arc::downgrade(&sender.0); - drop(sender); - // Create an extra ref to rust_future, simulating a waker that still holds a reference to - // it - let rust_future2 = rust_future.clone(); - - // Complete the rust future - rust_future.ffi_free(); - // Even though rust_future is still alive, the channel shouldn't be - assert!(Arc::strong_count(&rust_future2) > 0); - assert_eq!(channel_weak.strong_count(), 0); - assert!(channel_weak.upgrade().is_none()); - } - - // If `free` is called with a continuation still stored, we should call it them then. - // - // This shouldn't happen in practice, but it seems like good defensive programming - #[test] - fn test_complete_with_stored_continuation() { - let (_sender, rust_future) = channel(); - - let continuation_result = poll(&rust_future); - rust_future.ffi_free(); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - } - - // Test what happens if we see a `wake()` call while we're polling the future. This can - // happen, for example, with futures that are handled by a tokio thread pool. We should - // schedule another poll of the future in this case. - #[test] - fn test_wake_during_poll() { - let mut first_time = true; - let future = std::future::poll_fn(move |ctx| { - if first_time { - first_time = false; - // Wake the future while we are in the middle of polling it - ctx.waker().clone().wake(); - Poll::Pending - } else { - // The second time we're polled, we're ready - Poll::Ready("All done".to_owned()) - } - }); - let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = - RustFuture::new(future, crate::UniFfiTag); - let continuation_result = poll(&rust_future); - // The continuation function should called immediately - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); - // A second poll should finish the future - let continuation_result = poll(&rust_future); - assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); - let (return_buf, call_status) = complete(rust_future); - assert_eq!(call_status.code, RustCallStatusCode::Success); - assert_eq!( - <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), - "All done" - ); - } -} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs new file mode 100644 index 0000000000..93c34e7543 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs @@ -0,0 +1,320 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI. +//! +//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`. +//! +//! # The big picture +//! +//! We implement async foreign functions using a simplified version of the Future API: +//! +//! 0. At startup, register a [RustFutureContinuationCallback] by calling +//! rust_future_continuation_callback_set. +//! 1. Call the scaffolding function to get a [Handle] +//! 2a. In a loop: +//! - Call [rust_future_poll] +//! - Suspend the function until the [rust_future_poll] continuation function is called +//! - If the continuation was function was called with [RustFuturePoll::Ready], then break +//! otherwise continue. +//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the +//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to +//! enter a cancelled state. +//! 3. Call [rust_future_complete] to get the result of the future. +//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This: +//! - Releases any resources held by the future +//! - Calls any continuation callbacks that have not been called yet +//! +//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*` +//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C", +//! and manually monomorphized in the case of [rust_future_complete]. See +//! `uniffi_macros/src/setup_scaffolding.rs` for details. +//! +//! ## How does `Future` work exactly? +//! +//! A [`Future`] in Rust does nothing. When calling an async function, it just +//! returns a `Future` but nothing has happened yet. To start the computation, +//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if +//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically +//! means: +//! +//! > Please, try to poll me later, maybe the result will be ready! +//! +//! This model is very different than what other languages do, but it can actually +//! be translated quite easily, fortunately for us! +//! +//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does +//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the +//! `Future`: that's where they are polled. +//! +//! But… wait another minute… how does the executor know when to poll a [`Future`]? +//! Does it poll them randomly in an endless loop? Well, no, actually it depends +//! on the executor! A well-designed `Future` and executor work as follows. +//! Normally, when [`Future::poll`] is called, a [`Context`] argument is +//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a +//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will +//! signal the executor to poll a particular `Future`. A `Future` will clone +//! or pass-by-ref the waker to somewhere, as a callback, a completion, a +//! function, or anything, to the system that is responsible to notify when a +//! task is completed. So, to recap, the waker is _not_ responsible for waking the +//! `Future`, it _is_ responsible for _signaling_ the executor that a particular +//! `Future` should be polled again. That's why the documentation of +//! [`Poll::Pending`] specifies: +//! +//! > When a function returns `Pending`, the function must also ensure that the +//! > current task is scheduled to be awoken when progress can be made. +//! +//! “awakening” is done by using the `Waker`. +//! +//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html +//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll +//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready +//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending +//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html +//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html +//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html + +use std::{ + future::Future, + marker::PhantomData, + ops::Deref, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Wake}, +}; + +use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler}; +use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; + +/// Wraps the actual future we're polling +struct WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // Note: this could be a single enum, but that would make it easy to mess up the future pinning + // guarantee. For example you might want to call `std::mem::take()` to try to get the result, + // but if the future happened to be stored that would move and break all internal references. + future: Option<F>, + result: Option<Result<T::ReturnType, RustCallStatus>>, +} + +impl<F, T, UT> WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn new(future: F) -> Self { + Self { + future: Some(future), + result: None, + } + } + + // Poll the future and check if it's ready or not + fn poll(&mut self, context: &mut Context<'_>) -> bool { + if self.result.is_some() { + true + } else if let Some(future) = &mut self.future { + // SAFETY: We can call Pin::new_unchecked because: + // - This is the only time we get a &mut to `self.future` + // - We never poll the future after it's moved (for example by using take()) + // - We never move RustFuture, which contains us. + // - RustFuture is private to this module so no other code can move it. + let pinned = unsafe { Pin::new_unchecked(future) }; + // Run the poll and lift the result if it's ready + let mut out_status = RustCallStatus::default(); + let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status( + &mut out_status, + // This closure uses a `&mut F` value, which means it's not UnwindSafe by + // default. If the future panics, it may be in an invalid state. + // + // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None` + // case below and we will never poll the future again. + panic::AssertUnwindSafe(|| match pinned.poll(context) { + Poll::Pending => Ok(Poll::Pending), + Poll::Ready(v) => T::lower_return(v).map(Poll::Ready), + }), + ); + match result { + Some(Poll::Pending) => false, + Some(Poll::Ready(v)) => { + self.future = None; + self.result = Some(Ok(v)); + true + } + None => { + self.future = None; + self.result = Some(Err(out_status)); + true + } + } + } else { + log::error!("poll with neither future nor result set"); + true + } + } + + fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType { + let mut return_value = T::ReturnType::ffi_default(); + match self.result.take() { + Some(Ok(v)) => return_value = v, + Some(Err(call_status)) => *out_status = call_status, + None => *out_status = RustCallStatus::cancelled(), + } + self.free(); + return_value + } + + fn free(&mut self) { + self.future = None; + self.result = None; + } +} + +// If F and T are Send, then WrappedFuture is too +// +// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising +// that we will treat the raw pointer properly, for example by not returning it twice. +unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ +} + +/// Future that the foreign code is awaiting +pub(super) struct RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + // This Mutex should never block if our code is working correctly, since there should not be + // multiple threads calling [Self::poll] and/or [Self::complete] at the same time. + future: Mutex<WrappedFuture<F, T, UT>>, + scheduler: Mutex<Scheduler>, + // UT is used as the generic parameter for [LowerReturn]. + // Let's model this with PhantomData as a function that inputs a UT value. + _phantom: PhantomData<fn(UT) -> ()>, +} + +impl<F, T, UT> RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + pub(super) fn new(future: F, _tag: UT) -> Arc<Self> { + Arc::new(Self { + future: Mutex::new(WrappedFuture::new(future)), + scheduler: Mutex::new(Scheduler::new()), + _phantom: PhantomData, + }) + } + + pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { + let ready = self.is_cancelled() || { + let mut locked = self.future.lock().unwrap(); + let waker: std::task::Waker = Arc::clone(&self).into(); + locked.poll(&mut Context::from_waker(&waker)) + }; + if ready { + callback(data, RustFuturePoll::Ready) + } else { + self.scheduler.lock().unwrap().store(callback, data); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + self.scheduler.lock().unwrap().is_cancelled() + } + + pub(super) fn wake(&self) { + self.scheduler.lock().unwrap().wake(); + } + + pub(super) fn cancel(&self) { + self.scheduler.lock().unwrap().cancel(); + } + + pub(super) fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.future.lock().unwrap().complete(call_status) + } + + pub(super) fn free(self: Arc<Self>) { + // Call cancel() to send any leftover data to the continuation callback + self.scheduler.lock().unwrap().cancel(); + // Ensure we drop our inner future, releasing all held references + self.future.lock().unwrap().free(); + } +} + +impl<F, T, UT> Wake for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn wake(self: Arc<Self>) { + self.deref().wake() + } + + fn wake_by_ref(self: &Arc<Self>) { + self.deref().wake() + } +} + +/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to +/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this +/// allows you to use RustFuture functionality without knowing the concrete Future type, which is +/// unnamable. +/// +/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of +/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need +/// to create a poll, cancel, complete, and free scaffolding function for each exported async +/// function. That would add ~1kb binary size per exported function based on a quick estimate on a +/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and +/// only create those functions for each of the 13 possible FFI return types. +#[doc(hidden)] +pub trait RustFutureFfi<ReturnType>: Send + Sync { + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64); + fn ffi_cancel(&self); + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; + fn ffi_free(self: Arc<Self>); +} + +impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT> +where + // See rust_future_new for an explanation of these trait bounds + F: Future<Output = T> + Send + 'static, + T: LowerReturn<UT> + Send + 'static, + UT: Send + 'static, +{ + fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) { + self.poll(callback, data) + } + + fn ffi_cancel(&self) { + self.cancel() + } + + fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType { + self.complete(call_status) + } + + fn ffi_free(self: Arc<Self>) { + self.free(); + } +} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs new file mode 100644 index 0000000000..3d3505e5ef --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{future::Future, sync::Arc}; + +mod future; +mod scheduler; +use future::*; +use scheduler::*; + +#[cfg(test)] +mod tests; + +use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; + +/// Result code for [rust_future_poll]. This is passed to the continuation function. +#[repr(i8)] +#[derive(Debug, PartialEq, Eq)] +pub enum RustFuturePoll { + /// The future is ready and is waiting for [rust_future_complete] to be called + Ready = 0, + /// The future might be ready and [rust_future_poll] should be called again + MaybeReady = 1, +} + +/// Foreign callback that's passed to [rust_future_poll] +/// +/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again +/// to continue progress on the future. +pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll); + +// === Public FFI API === + +/// Create a new [Handle] for a Rust future +/// +/// For each exported async function, UniFFI will create a scaffolding function that uses this to +/// create the [Handle] to pass to the foreign code. +pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> Handle +where + // F is the future type returned by the exported async function. It needs to be Send + `static + // since it will move between threads for an indeterminate amount of time as the foreign + // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, + // since we synchronize all access to the values. + F: Future<Output = T> + Send + 'static, + // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + + // 'static for the same reason as F. + T: LowerReturn<UT> + Send + 'static, + // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. + UT: Send + 'static, + // Needed to allocate a handle + dyn RustFutureFfi<T::ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle( + RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>> + ) +} + +/// Poll a Rust future +/// +/// When the future is ready to progress the continuation will be called with the `data` value and +/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called +/// exactly once. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll<ReturnType, UT>( + handle: Handle, + callback: RustFutureContinuationCallback, + data: u64, +) where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_poll(callback, data) +} + +/// Cancel a Rust future +/// +/// Any current and future continuations will be immediately called with RustFuturePoll::Ready. +/// +/// This is needed for languages like Swift, which continuation to wait for the continuation to be +/// called when tasks are cancelled. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel<ReturnType, UT>(handle: Handle) +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_cancel() +} + +/// Complete a Rust future +/// +/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for +/// each supported FFI type. +/// +/// # Safety +/// +/// - The [Handle] must not previously have been passed to [rust_future_free] +/// - The `T` param must correctly correspond to the [rust_future_new] call. It must +/// be `<Output as LowerReturn<UT>>::ReturnType` +pub unsafe fn rust_future_complete<ReturnType, UT>( + handle: Handle, + out_status: &mut RustCallStatus, +) -> ReturnType +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_complete(out_status) +} + +/// Free a Rust future, dropping the strong reference and releasing all references held by the +/// future. +/// +/// # Safety +/// +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free<ReturnType, UT>(handle: Handle) +where + dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>, +{ + <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::consume_handle(handle).ffi_free() +} + +// Derive HandleAlloc for dyn RustFutureFfi<T> for all FFI return types +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u8>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i8>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u16>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i16>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f32>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f64>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<*const std::ffi::c_void>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<crate::RustBuffer>); +derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<()>); diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs new file mode 100644 index 0000000000..629ee0c109 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::mem; + +use super::{RustFutureContinuationCallback, RustFuturePoll}; + +/// Schedules a [crate::RustFuture] by managing the continuation data +/// +/// This struct manages the continuation callback and data that comes from the foreign side. It +/// is responsible for calling the continuation callback when the future is ready to be woken up. +/// +/// The basic guarantees are: +/// +/// * Each callback will be invoked exactly once, with its associated data. +/// * If `wake()` is called, the callback will be invoked to wake up the future -- either +/// immediately or the next time we get a callback. +/// * If `cancel()` is called, the same will happen and the schedule will stay in the cancelled +/// state, invoking any future callbacks as soon as they're stored. + +#[derive(Debug)] +pub(super) enum Scheduler { + /// No continuations set, neither wake() nor cancel() called. + Empty, + /// `wake()` was called when there was no continuation set. The next time `store` is called, + /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady` + Waked, + /// The future has been cancelled, any future `store` calls should immediately result in the + /// continuation being called with `RustFuturePoll::Ready`. + Cancelled, + /// Continuation set, the next time `wake()` is called is called, we should invoke it. + Set(RustFutureContinuationCallback, u64), +} + +impl Scheduler { + pub(super) fn new() -> Self { + Self::Empty + } + + /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or + /// `Cancelled` state, call the continuation immediately with the data. + pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: u64) { + match self { + Self::Empty => *self = Self::Set(callback, data), + Self::Set(old_callback, old_data) => { + log::error!( + "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?" + ); + old_callback(*old_data, RustFuturePoll::Ready); + *self = Self::Set(callback, data); + } + Self::Waked => { + *self = Self::Empty; + callback(data, RustFuturePoll::MaybeReady); + } + Self::Cancelled => { + callback(data, RustFuturePoll::Ready); + } + } + } + + pub(super) fn wake(&mut self) { + match self { + // If we had a continuation set, then call it and transition to the `Empty` state. + Self::Set(callback, old_data) => { + let old_data = *old_data; + let callback = *callback; + *self = Self::Empty; + callback(old_data, RustFuturePoll::MaybeReady); + } + // If we were in the `Empty` state, then transition to `Waked`. The next time `store` + // is called, we will immediately call the continuation. + Self::Empty => *self = Self::Waked, + // This is a no-op if we were in the `Cancelled` or `Waked` state. + _ => (), + } + } + + pub(super) fn cancel(&mut self) { + if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) { + callback(old_data, RustFuturePoll::Ready); + } + } + + pub(super) fn is_cancelled(&self) -> bool { + matches!(self, Self::Cancelled) + } +} + +// The `*const ()` data pointer references an object on the foreign side. +// This object must be `Sync` in Rust terminology -- it must be safe for us to pass the pointer to the continuation callback from any thread. +// If the foreign side upholds their side of the contract, then `Scheduler` is Send + Sync. + +unsafe impl Send for Scheduler {} +unsafe impl Sync for Scheduler {} diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs new file mode 100644 index 0000000000..886ee27c71 --- /dev/null +++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs @@ -0,0 +1,223 @@ +use once_cell::sync::OnceCell; +use std::{ + future::Future, + panic, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll, Waker}, +}; + +use super::*; +use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode}; + +// Sender/Receiver pair that we use for testing +struct Channel { + result: Option<Result<String, TestError>>, + waker: Option<Waker>, +} + +struct Sender(Arc<Mutex<Channel>>); + +impl Sender { + fn wake(&self) { + let inner = self.0.lock().unwrap(); + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } + + fn send(&self, value: Result<String, TestError>) { + let mut inner = self.0.lock().unwrap(); + if inner.result.replace(value).is_some() { + panic!("value already sent"); + } + if let Some(waker) = &inner.waker { + waker.wake_by_ref(); + } + } +} + +struct Receiver(Arc<Mutex<Channel>>); + +impl Future for Receiver { + type Output = Result<String, TestError>; + + fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<String, TestError>> { + let mut inner = self.0.lock().unwrap(); + match &inner.result { + Some(v) => Poll::Ready(v.clone()), + None => { + inner.waker = Some(context.waker().clone()); + Poll::Pending + } + } + } +} + +// Create a sender and rust future that we can use for testing +fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) { + let channel = Arc::new(Mutex::new(Channel { + result: None, + waker: None, + })); + let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag); + (Sender(channel), rust_future) +} + +/// Poll a Rust future and get an OnceCell that's set when the continuation is called +fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> { + let cell = Arc::new(OnceCell::new()); + let handle = Arc::into_raw(cell.clone()) as u64; + rust_future.clone().ffi_poll(poll_continuation, handle); + cell +} + +extern "C" fn poll_continuation(data: u64, code: RustFuturePoll) { + let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) }; + cell.set(code).expect("Error setting OnceCell"); +} + +fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) { + let mut out_status_code = RustCallStatus::default(); + let return_value = rust_future.ffi_complete(&mut out_status_code); + (return_value, out_status_code) +} + +#[test] +fn test_success() { + let (sender, rust_future) = channel(); + + // Test polling the rust future before it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.wake(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Test polling the rust future when it's ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Ok("All done".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + // Future polls should immediately return ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Complete the future + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); +} + +#[test] +fn test_error() { + let (sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + sender.send(Err("Something went wrong".into())); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Error); + unsafe { + assert_eq!( + <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer( + call_status.error_buf.assume_init() + ) + .unwrap(), + TestError::from("Something went wrong"), + ) + } +} + +// Once `complete` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_cancel() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), None); + rust_future.ffi_cancel(); + // Cancellation should immediately invoke the callback with RustFuturePoll::Ready + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + // Future polls should immediately invoke the callback with RustFuturePoll::Ready + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + + let (_, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Cancelled); +} + +// Once `free` is called, the inner future should be released, even if wakers still hold a +// reference to the RustFuture +#[test] +fn test_release_future() { + let (sender, rust_future) = channel(); + // Create a weak reference to the channel to use to check if rust_future has dropped its + // future. + let channel_weak = Arc::downgrade(&sender.0); + drop(sender); + // Create an extra ref to rust_future, simulating a waker that still holds a reference to + // it + let rust_future2 = rust_future.clone(); + + // Complete the rust future + rust_future.ffi_free(); + // Even though rust_future is still alive, the channel shouldn't be + assert!(Arc::strong_count(&rust_future2) > 0); + assert_eq!(channel_weak.strong_count(), 0); + assert!(channel_weak.upgrade().is_none()); +} + +// If `free` is called with a continuation still stored, we should call it them then. +// +// This shouldn't happen in practice, but it seems like good defensive programming +#[test] +fn test_complete_with_stored_continuation() { + let (_sender, rust_future) = channel(); + + let continuation_result = poll(&rust_future); + rust_future.ffi_free(); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); +} + +// Test what happens if we see a `wake()` call while we're polling the future. This can +// happen, for example, with futures that are handled by a tokio thread pool. We should +// schedule another poll of the future in this case. +#[test] +fn test_wake_during_poll() { + let mut first_time = true; + let future = std::future::poll_fn(move |ctx| { + if first_time { + first_time = false; + // Wake the future while we are in the middle of polling it + ctx.waker().clone().wake(); + Poll::Pending + } else { + // The second time we're polled, we're ready + Poll::Ready("All done".to_owned()) + } + }); + let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = RustFuture::new(future, crate::UniFfiTag); + let continuation_result = poll(&rust_future); + // The continuation function should called immediately + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady)); + // A second poll should finish the future + let continuation_result = poll(&rust_future); + assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready)); + let (return_buf, call_status) = complete(rust_future); + assert_eq!(call_status.code, RustCallStatusCode::Success); + assert_eq!( + <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(), + "All done" + ); +} diff --git a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs index af18f3873b..aec093154a 100644 --- a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs +++ b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs @@ -20,11 +20,11 @@ /// /// This crate needs to implement `FFIConverter<UT>` on `UniFfiTag` instances for all UniFFI /// consumer crates. To do this, it defines blanket impls like `impl<UT> FFIConverter<UT> for u8`. -/// "UT" means an abitrary `UniFfiTag` type. +/// "UT" means an arbitrary `UniFfiTag` type. use crate::{ check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata, - ConvertError, FfiConverter, ForeignExecutor, Lift, LiftReturn, Lower, LowerReturn, - MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError, + ConvertError, FfiConverter, Lift, LiftRef, LiftReturn, Lower, LowerReturn, MetadataBuffer, + Result, RustBuffer, UnexpectedUniFFICallbackError, }; use anyhow::bail; use bytes::buf::{Buf, BufMut}; @@ -405,47 +405,6 @@ where .concat(V::TYPE_ID_META); } -/// FFI support for [ForeignExecutor] -/// -/// These are passed over the FFI as opaque pointer-sized types representing the foreign executor. -/// The foreign bindings may use an actual pointer to the executor object, or a usized integer -/// handle. -unsafe impl<UT> FfiConverter<UT> for ForeignExecutor { - type FfiType = crate::ForeignExecutorHandle; - - // Passing these back to the foreign bindings is currently not supported - fn lower(executor: Self) -> Self::FfiType { - executor.handle - } - - fn write(executor: Self, buf: &mut Vec<u8>) { - // Use native endian when writing these values, so they can be casted to pointer values - match std::mem::size_of::<usize>() { - // Use native endian when reading these values, so they can be casted to pointer values - 4 => buf.put_u32_ne(executor.handle.0 as u32), - 8 => buf.put_u64_ne(executor.handle.0 as u64), - n => panic!("Invalid usize width: {n}"), - }; - } - - fn try_lift(executor: Self::FfiType) -> Result<Self> { - Ok(ForeignExecutor::new(executor)) - } - - fn try_read(buf: &mut &[u8]) -> Result<Self> { - let usize_val = match std::mem::size_of::<usize>() { - // Use native endian when reading these values, so they can be casted to pointer values - 4 => buf.get_u32_ne() as usize, - 8 => buf.get_u64_ne() as usize, - n => panic!("Invalid usize width: {n}"), - }; - <Self as FfiConverter<UT>>::try_lift(crate::ForeignExecutorHandle(usize_val as *const ())) - } - - const TYPE_ID_META: MetadataBuffer = - MetadataBuffer::from_code(metadata::codes::TYPE_FOREIGN_EXECUTOR); -} - derive_ffi_traits!(blanket u8); derive_ffi_traits!(blanket i8); derive_ffi_traits!(blanket u16); @@ -460,7 +419,6 @@ derive_ffi_traits!(blanket bool); derive_ffi_traits!(blanket String); derive_ffi_traits!(blanket Duration); derive_ffi_traits!(blanket SystemTime); -derive_ffi_traits!(blanket ForeignExecutor); // For composite types, derive LowerReturn, LiftReturn, etc, from Lift/Lower. // @@ -498,7 +456,11 @@ unsafe impl<UT> LowerReturn<UT> for () { } unsafe impl<UT> LiftReturn<UT> for () { - fn lift_callback_return(_buf: RustBuffer) -> Self {} + type ReturnType = (); + + fn try_lift_successful_return(_: ()) -> Result<Self> { + Ok(()) + } const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT); } @@ -535,13 +497,15 @@ where unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E> where R: LiftReturn<UT>, - E: Lift<UT> + ConvertError<UT>, + E: Lift<UT, FfiType = RustBuffer> + ConvertError<UT>, { - fn lift_callback_return(buf: RustBuffer) -> Self { - Ok(R::lift_callback_return(buf)) + type ReturnType = R::ReturnType; + + fn try_lift_successful_return(v: R::ReturnType) -> Result<Self> { + R::try_lift_successful_return(v).map(Ok) } - fn lift_callback_error(buf: RustBuffer) -> Self { + fn lift_error(buf: RustBuffer) -> Self { match E::try_lift_from_rust_buffer(buf) { Ok(lifted_error) => Err(lifted_error), Err(anyhow_error) => { @@ -560,3 +524,14 @@ where .concat(R::TYPE_ID_META) .concat(E::TYPE_ID_META); } + +unsafe impl<T, UT> LiftRef<UT> for [T] +where + T: Lift<UT>, +{ + type LiftType = Vec<T>; +} + +unsafe impl<UT> LiftRef<UT> for str { + type LiftType = String; +} diff --git a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs index 3b5914e32f..4e7b9e06fa 100644 --- a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs +++ b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs @@ -51,7 +51,10 @@ use std::{borrow::Borrow, sync::Arc}; use anyhow::bail; use bytes::Buf; -use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; +use crate::{ + FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, + UnexpectedUniFFICallbackError, +}; /// Generalized FFI conversions /// @@ -302,14 +305,41 @@ pub unsafe trait LowerReturn<UT>: Sized { /// These traits should not be used directly, only in generated code, and the generated code should /// have fixture tests to test that everything works correctly together. pub unsafe trait LiftReturn<UT>: Sized { - /// Lift a Rust value for a callback interface method result - fn lift_callback_return(buf: RustBuffer) -> Self; + /// FFI return type for trait interfaces + type ReturnType; + + /// Lift a successfully returned value from a trait interface + fn try_lift_successful_return(v: Self::ReturnType) -> Result<Self>; + + /// Lift a foreign returned value from a trait interface + /// + /// When we call a foreign-implemented trait interface method, we pass a &mut RustCallStatus + /// and get [Self::ReturnType] returned. This method takes both of those and lifts `Self` from + /// it. + fn lift_foreign_return(ffi_return: Self::ReturnType, call_status: RustCallStatus) -> Self { + match call_status.code { + RustCallStatusCode::Success => Self::try_lift_successful_return(ffi_return) + .unwrap_or_else(|e| { + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + }), + RustCallStatusCode::Error => { + Self::lift_error(unsafe { call_status.error_buf.assume_init() }) + } + _ => { + let e = <String as FfiConverter<crate::UniFfiTag>>::try_lift(unsafe { + call_status.error_buf.assume_init() + }) + .unwrap_or_else(|e| format!("(Error lifting message: {e}")); + Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e)) + } + } + } /// Lift a Rust value for a callback interface method error result /// /// This is called for "expected errors" -- the callback method returns a Result<> type and the /// foreign code throws an exception that corresponds to the error type. - fn lift_callback_error(_buf: RustBuffer) -> Self { + fn lift_error(_buf: RustBuffer) -> Self { panic!("Callback interface method returned unexpected error") } @@ -351,6 +381,66 @@ pub trait ConvertError<UT>: Sized { fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>; } +/// Manage handles for `Arc<Self>` instances +/// +/// Handles are used to manage objects that are passed across the FFI. They general usage is: +/// +/// * Rust creates an `Arc<>` +/// * Rust uses `new_handle` to create a handle that represents the Arc reference +/// * Rust passes the handle to the foreign code as a `u64` +/// * The foreign code passes the handle back to `Rust` to refer to the object: +/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an +/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc` +/// to get a new `Arc<>` clone for it. +/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign +/// code either stops using the handle after returning it or calls `clone_handle` and returns +/// the clone. +/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI +/// function. This functions input an owned handle and consume it. +/// +/// The foreign code also defines their own handles. These represent foreign objects that are +/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse. +/// +/// Handles must always be `Send` and the objects they reference must always be `Sync`. +/// This means that it must be safe to send handles to other threads and use them there. +/// +/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`. +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait HandleAlloc<UT>: Send + Sync { + /// Create a new handle for an Arc value + /// + /// Use this to lower an Arc into a handle value before passing it across the FFI. + /// The newly-created handle will have reference count = 1. + fn new_handle(value: Arc<Self>) -> Handle; + + /// Clone a handle + /// + /// This creates a new handle from an existing one. + /// It's used when the foreign code wants to pass back an owned handle and still keep a copy + /// for themselves. + fn clone_handle(handle: Handle) -> Handle; + + /// Get a clone of the `Arc<>` using a "borrowed" handle. + /// + /// Take care that the handle can not be destroyed between when it's passed and when + /// `get_arc()` is called. #1797 is a cautionary tale. + fn get_arc(handle: Handle) -> Arc<Self> { + Self::consume_handle(Self::clone_handle(handle)) + } + + /// Consume a handle, getting back the initial `Arc<>` + fn consume_handle(handle: Handle) -> Arc<Self>; +} + /// Derive FFI traits /// /// This can be used to derive: @@ -439,9 +529,10 @@ macro_rules! derive_ffi_traits { (impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)* { - fn lift_callback_return(buf: $crate::RustBuffer) -> Self { - <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf) - .expect("Error reading callback interface result") + type ReturnType = <Self as $crate::Lift<$ut>>::FfiType; + + fn try_lift_successful_return(v: Self::ReturnType) -> $crate::Result<Self> { + <Self as $crate::Lift<$ut>>::try_lift(v) } const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META; @@ -463,4 +554,50 @@ macro_rules! derive_ffi_traits { } } }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + // Derived HandleAlloc implementation. + // + // This is only needed for !Sized types like `dyn Trait`, below is a blanket implementation + // for any sized type. + unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)* + { + // To implement HandleAlloc for an unsized type, wrap it with a second Arc which + // converts the wide pointer into a normal pointer. + + fn new_handle(value: ::std::sync::Arc<Self>) -> $crate::Handle { + $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value))) + } + + fn clone_handle(handle: $crate::Handle) -> $crate::Handle { + unsafe { + ::std::sync::Arc::<::std::sync::Arc<Self>>::increment_strong_count(handle.as_pointer::<::std::sync::Arc<Self>>()); + } + handle + } + + fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc<Self> { + unsafe { + ::std::sync::Arc::<Self>::clone( + &std::sync::Arc::<::std::sync::Arc::<Self>>::from_raw(handle.as_pointer::<::std::sync::Arc<Self>>()) + ) + } + } + } + }; +} + +unsafe impl<T: Send + Sync, UT> HandleAlloc<UT> for T { + fn new_handle(value: Arc<Self>) -> Handle { + Handle::from_pointer(Arc::into_raw(value)) + } + + fn clone_handle(handle: Handle) -> Handle { + unsafe { Arc::increment_strong_count(handle.as_pointer::<T>()) }; + handle + } + + fn consume_handle(handle: Handle) -> Arc<Self> { + unsafe { Arc::from_raw(handle.as_pointer()) } + } } diff --git a/third_party/rust/uniffi_core/src/lib.rs b/third_party/rust/uniffi_core/src/lib.rs index c84b403dce..1f3a2403f8 100644 --- a/third_party/rust/uniffi_core/src/lib.rs +++ b/third_party/rust/uniffi_core/src/lib.rs @@ -45,7 +45,8 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ - ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, + ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower, + LowerReturn, }; pub use metadata::*; @@ -57,9 +58,8 @@ pub mod deps { pub use async_compat; pub use bytes; pub use log; + pub use oneshot; pub use static_assertions; - // Export this dependency for the 0.25 branch so that we can use it in `setup_scaffolding.rs` - pub use once_cell; } mod panichook; diff --git a/third_party/rust/uniffi_core/src/metadata.rs b/third_party/rust/uniffi_core/src/metadata.rs index 770d2b36d5..dc61a1bfcb 100644 --- a/third_party/rust/uniffi_core/src/metadata.rs +++ b/third_party/rust/uniffi_core/src/metadata.rs @@ -32,13 +32,14 @@ pub mod codes { pub const RECORD: u8 = 2; pub const ENUM: u8 = 3; pub const INTERFACE: u8 = 4; - pub const ERROR: u8 = 5; pub const NAMESPACE: u8 = 6; pub const CONSTRUCTOR: u8 = 7; pub const UDL_FILE: u8 = 8; pub const CALLBACK_INTERFACE: u8 = 9; pub const TRAIT_METHOD: u8 = 10; pub const UNIFFI_TRAIT: u8 = 11; + pub const TRAIT_INTERFACE: u8 = 12; + pub const CALLBACK_TRAIT_INTERFACE: u8 = 13; pub const UNKNOWN: u8 = 255; // Type codes @@ -66,20 +67,24 @@ pub mod codes { pub const TYPE_CALLBACK_INTERFACE: u8 = 21; pub const TYPE_CUSTOM: u8 = 22; pub const TYPE_RESULT: u8 = 23; - pub const TYPE_FUTURE: u8 = 24; - pub const TYPE_FOREIGN_EXECUTOR: u8 = 25; + pub const TYPE_TRAIT_INTERFACE: u8 = 24; + pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25; pub const TYPE_UNIT: u8 = 255; - // Literal codes for LiteralMetadata - note that we don't support - // all variants in the "emit/reader" context. + // Literal codes for LiteralMetadata pub const LIT_STR: u8 = 0; pub const LIT_INT: u8 = 1; pub const LIT_FLOAT: u8 = 2; pub const LIT_BOOL: u8 = 3; - pub const LIT_NULL: u8 = 4; + pub const LIT_NONE: u8 = 4; + pub const LIT_SOME: u8 = 5; + pub const LIT_EMPTY_SEQ: u8 = 6; } -const BUF_SIZE: usize = 4096; +// For large errors (e.g. enums) a buffer size of ~4k - ~8k +// is not enough. See issues on Github: #1968 and #2041 and +// for an example see fixture/large-error +const BUF_SIZE: usize = 16384; // This struct is a kludge around the fact that Rust const generic support doesn't quite handle our // needs. @@ -168,7 +173,17 @@ impl MetadataBuffer { self.concat_value(value as u8) } - // Concatenate a string to this buffer. + // Option<bool> + pub const fn concat_option_bool(self, value: Option<bool>) -> Self { + self.concat_value(match value { + None => 0, + Some(false) => 1, + Some(true) => 2, + }) + } + + // Concatenate a string to this buffer. The maximum string length is 255 bytes. For longer strings, + // use `concat_long_str()`. // // Strings are encoded as a `u8` length, followed by the utf8 data. // @@ -189,6 +204,28 @@ impl MetadataBuffer { self } + // Concatenate a longer string to this buffer. + // + // Strings are encoded as a `u16` length, followed by the utf8 data. + // + // This consumes self, which is convenient for the proc-macro code and also allows us to avoid + // allocated an extra buffer. + pub const fn concat_long_str(mut self, string: &str) -> Self { + assert!(self.size + string.len() + 1 < BUF_SIZE); + let [lo, hi] = (string.len() as u16).to_le_bytes(); + self.bytes[self.size] = lo; + self.bytes[self.size + 1] = hi; + self.size += 2; + let bytes = string.as_bytes(); + let mut i = 0; + while i < bytes.len() { + self.bytes[self.size] = bytes[i]; + self.size += 1; + i += 1; + } + self + } + // Create an array from this MetadataBuffer // // SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust diff --git a/third_party/rust/uniffi_macros/.cargo-checksum.json b/third_party/rust/uniffi_macros/.cargo-checksum.json index 96e44ac74e..9db049289c 100644 --- a/third_party/rust/uniffi_macros/.cargo-checksum.json +++ b/third_party/rust/uniffi_macros/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"376811e12479a0cced9375a12a2e3186da053b3c7520374bf6f2b54d23282082","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/enum_.rs":"2d3181ef22468deb4e8f48b7b1ed9421134604c2c226d5084c453ebb2cedbfd5","src/error.rs":"0b5beb8a2c8c93c30c56f2f80538bf1f1c8e6a99b2b1c934ad12a4feb75a2fc0","src/export.rs":"6d05417f0b10a9d6df9e96a6ed771c89a5e59e6e52d1ce812025bfe68e9f717e","src/export/attributes.rs":"53a27264882ab0a802a0ee109a2ea3f3456d4c83c85eaa5c0f5912d4486ab843","src/export/callback_interface.rs":"ad2782b7ca930dc067c391394480362be1fbf331d8786be089c0a87415c85a88","src/export/item.rs":"a7b74e6400ec6c5e8fb09d8842ce718b9555d75de13fdf5fecbab2fceeec7cbf","src/export/scaffolding.rs":"8ab2b9b0c5ad5b5477963843b7a58d496344da7de1a2a4b07f30f22275c8f3c9","src/export/utrait.rs":"ce4a3d629aaf0b44b8c5ce6794c5d5b0d7f86f46f0dd6b6ecb134514be330f0d","src/fnsig.rs":"886ceec806b429c7d86fe00d0d84f7b04e21142605f7a61d182f9f616210cd2b","src/lib.rs":"501c736647eff2705c5565f80e554d2e440cceceed95044c9fe147fc309afb48","src/object.rs":"0a14d6b8ccb4faef93a1f61a97ac7d47a80b6581383f4a6e0a4789f53e66e630","src/record.rs":"fbff287bb2d0b7a9eb35ef3161fbd1abbc21f3aa08e88bd4242dd12acbfa3ee9","src/setup_scaffolding.rs":"60b48a56fae16cf01824586b90e6440da3362135d98fc07aaed624c57f806163","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"217ecef0e4dabd158a7597aa3d00d94477993a90b388afbbc0fb39e14f6b013e"},"package":"11cf7a58f101fcedafa5b77ea037999b88748607f0ef3a33eaa0efc5392e92e4"}
\ No newline at end of file +{"files":{"Cargo.toml":"a292239ca3c72852768fdf0e7bc2dd6386af7bf1ab0ef56dff01e1c9e781b2ca","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/default.rs":"77466ac54da69094bcdccc5927d0980b1e9dd0095647ca825830673c48847a53","src/enum_.rs":"afe0a6534d8e7f68047e3f1afad9369d5d5650f4c7555e8d4173f24126c715ba","src/error.rs":"30168378da9a23e6530ffe68647bf6618d07a0aaa236d5009137a922798a0e88","src/export.rs":"42c5e784c1dccc796c8b6ea29c2dc1811e48a531488a3ed0e2a59330778a7e41","src/export/attributes.rs":"c848f8c309c4cf7a168f038834752dc4816b5c853768d7c331ea4cd5ce0841b7","src/export/callback_interface.rs":"794b0665dc7eb02ea854c61c8bb2781e0b4ac1de646d95a8fd7791f770f2e6e3","src/export/item.rs":"4e86875692c2d2993fde12e78dbde2cbffa5675ede143577d5620126401efe05","src/export/scaffolding.rs":"b25167d2213b6d6c5ba653622f26791e8c3e74a5ecce6512ec27009fc8bf68e4","src/export/trait_interface.rs":"f07f9908ee28661de4586d89b693f3d93dae5e5cba8a089eff25f20bbf6b373b","src/export/utrait.rs":"b55533d3eef8262944d3c0d9a3a9cba0615d2d5af8608f0919abc7699989e2a8","src/fnsig.rs":"5e434a1cc87166c5245424bb14e896eb766bf680d4d50d4b8536852f91487d7c","src/lib.rs":"a28bbfd2d1dc835306ff6072f75761bb6b3a158477bba966057776c527fe6d70","src/object.rs":"5419ed64c8120aef811a77c2205f58a7a537bdf34ae04f9c92dd3aaa176eed39","src/record.rs":"29072542cc2f3e027bd7c59b45ba913458f8213d1b2b33bc70d140baa98fcdc8","src/setup_scaffolding.rs":"173fdc916967d54bd6532def16d12e5bb85467813a46a031d3338b77625756bb","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"a2c3693343e78dffb2a7f7b39eeb9b7f298b66688f1766a7c08113cf9431ef4c"},"package":"18331d35003f46f0d04047fbe4227291815b83a937a8c32bc057f990962182c4"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_macros/Cargo.toml b/third_party/rust/uniffi_macros/Cargo.toml index 9d3908ae8d..5ae193e392 100644 --- a/third_party/rust/uniffi_macros/Cargo.toml +++ b/third_party/rust/uniffi_macros/Cargo.toml @@ -12,11 +12,12 @@ [package] edition = "2021" name = "uniffi_macros" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust (convenience macros)" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -47,6 +48,7 @@ version = "1.0" [dependencies.serde] version = "1.0.136" +features = ["derive"] [dependencies.syn] version = "2.0" @@ -59,11 +61,13 @@ features = [ version = "0.5.9" [dependencies.uniffi_build] -version = "=0.25.3" +version = "=0.27.1" +optional = true [dependencies.uniffi_meta] -version = "=0.25.3" +version = "=0.27.1" [features] default = [] nightly = [] +trybuild = ["dep:uniffi_build"] diff --git a/third_party/rust/uniffi_macros/README.md b/third_party/rust/uniffi_macros/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_macros/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_macros/src/default.rs b/third_party/rust/uniffi_macros/src/default.rs new file mode 100644 index 0000000000..000c205845 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/default.rs @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::util::kw; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + bracketed, parenthesized, + parse::{Nothing, Parse, ParseStream}, + token::{Bracket, Paren}, + Lit, +}; + +/// Default value +#[derive(Clone)] +pub enum DefaultValue { + Literal(Lit), + None(kw::None), + Some { + some: kw::Some, + paren: Paren, + inner: Box<DefaultValue>, + }, + EmptySeq(Bracket), +} + +impl ToTokens for DefaultValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + DefaultValue::Literal(lit) => lit.to_tokens(tokens), + DefaultValue::None(kw) => kw.to_tokens(tokens), + DefaultValue::Some { inner, .. } => tokens.extend(quote! { Some(#inner) }), + DefaultValue::EmptySeq(_) => tokens.extend(quote! { [] }), + } + } +} + +impl Parse for DefaultValue { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::None) { + let none_kw: kw::None = input.parse()?; + Ok(Self::None(none_kw)) + } else if lookahead.peek(kw::Some) { + let some: kw::Some = input.parse()?; + let content; + let paren = parenthesized!(content in input); + Ok(Self::Some { + some, + paren, + inner: content.parse()?, + }) + } else if lookahead.peek(Bracket) { + let content; + let bracket = bracketed!(content in input); + content.parse::<Nothing>()?; + Ok(Self::EmptySeq(bracket)) + } else { + Ok(Self::Literal(input.parse()?)) + } + } +} + +impl DefaultValue { + fn metadata_calls(&self) -> syn::Result<TokenStream> { + match self { + DefaultValue::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err( + syn::Error::new_spanned(i, "integer literals with suffix not supported here"), + ), + DefaultValue::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err( + syn::Error::new_spanned(f, "float literals with suffix not supported here"), + ), + + DefaultValue::Literal(Lit::Str(s)) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_STR) + .concat_str(#s) + }), + DefaultValue::Literal(Lit::Int(i)) => { + let digits = i.base10_digits(); + Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_INT) + .concat_str(#digits) + }) + } + DefaultValue::Literal(Lit::Float(f)) => { + let digits = f.base10_digits(); + Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_FLOAT) + .concat_str(#digits) + }) + } + DefaultValue::Literal(Lit::Bool(b)) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_BOOL) + .concat_bool(#b) + }), + + DefaultValue::Literal(_) => Err(syn::Error::new_spanned( + self, + "this type of literal is not currently supported as a default", + )), + + DefaultValue::EmptySeq(_) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_EMPTY_SEQ) + }), + + DefaultValue::None(_) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_NONE) + }), + + DefaultValue::Some { inner, .. } => { + let inner_calls = inner.metadata_calls()?; + Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_SOME) + #inner_calls + }) + } + } + } +} + +pub fn default_value_metadata_calls(default: &Option<DefaultValue>) -> syn::Result<TokenStream> { + Ok(match default { + Some(default) => { + let metadata_calls = default.metadata_calls()?; + quote! { + .concat_bool(true) + #metadata_calls + } + } + None => quote! { .concat_bool(false) }, + }) +} diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs index 32abfa08cc..fd98da3129 100644 --- a/third_party/rust/uniffi_macros/src/enum_.rs +++ b/third_party/rust/uniffi_macros/src/enum_.rs @@ -1,13 +1,47 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Data, DataEnum, DeriveInput, Field, Index}; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, Variant, +}; use crate::util::{ - create_metadata_items, derive_all_ffi_traits, ident_to_string, mod_path, tagged_impl_header, - try_metadata_value_from_usize, try_read_field, + create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring, + ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header, + try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs, }; -pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> { +fn extract_repr(attrs: &[Attribute]) -> syn::Result<Option<Ident>> { + let mut result = None; + for attr in attrs { + if attr.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + result = match meta.path.get_ident() { + Some(i) => { + let s = i.to_string(); + match s.as_str() { + "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" + | "i64" | "isize" => Some(i.clone()), + // while the default repr for an enum is `isize` we don't apply that default here. + _ => None, + } + } + _ => None, + }; + Ok(()) + })? + } + } + Ok(result) +} + +pub fn expand_enum( + input: DeriveInput, + // Attributes from #[derive_error_for_udl()], if we are in udl mode + attr_from_udl_mode: Option<EnumAttr>, + udl_mode: bool, +) -> syn::Result<TokenStream> { let enum_ = match input.data { Data::Enum(e) => e, _ => { @@ -18,10 +52,17 @@ pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStrea } }; let ident = &input.ident; - let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode); + let docstring = extract_docstring(&input.attrs)?; + let discr_type = extract_repr(&input.attrs)?; + let mut attr: EnumAttr = input.attrs.parse_uniffi_attr_args()?; + if let Some(attr_from_udl_mode) = attr_from_udl_mode { + attr = attr.merge(attr_from_udl_mode)?; + } + let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode, &attr); let meta_static_var = (!udl_mode).then(|| { - enum_meta_static_var(ident, &enum_).unwrap_or_else(syn::Error::into_compile_error) + enum_meta_static_var(ident, docstring, discr_type, &enum_, &attr) + .unwrap_or_else(syn::Error::into_compile_error) }); Ok(quote! { @@ -34,11 +75,13 @@ pub(crate) fn enum_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, + attr: &EnumAttr, ) -> TokenStream { enum_or_error_ffi_converter_impl( ident, enum_, udl_mode, + attr, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } @@ -47,11 +90,13 @@ pub(crate) fn rich_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, + attr: &EnumAttr, ) -> TokenStream { enum_or_error_ffi_converter_impl( ident, enum_, udl_mode, + attr, quote! { ::uniffi::metadata::codes::TYPE_ENUM }, ) } @@ -60,6 +105,7 @@ fn enum_or_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, + attr: &EnumAttr, metadata_type_code: TokenStream, ) -> TokenStream { let name = ident_to_string(ident); @@ -69,19 +115,50 @@ fn enum_or_error_ffi_converter_impl( Ok(p) => p, Err(e) => return e.into_compile_error(), }; - let write_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { - let v_ident = &v.ident; - let fields = v.fields.iter().map(|f| &f.ident); - let idx = Index::from(i + 1); - let write_fields = v.fields.iter().map(write_field); + let mut write_match_arms: Vec<_> = enum_ + .variants + .iter() + .enumerate() + .map(|(i, v)| { + let v_ident = &v.ident; + let field_idents = v + .fields + .iter() + .enumerate() + .map(|(i, f)| { + f.ident + .clone() + .unwrap_or_else(|| Ident::new(&format!("e{i}"), f.span())) + }) + .collect::<Vec<Ident>>(); + let idx = Index::from(i + 1); + let write_fields = + std::iter::zip(v.fields.iter(), field_idents.iter()).map(|(f, ident)| { + let ty = &f.ty; + quote! { + <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf); + } + }); + let is_tuple = v.fields.iter().any(|f| f.ident.is_none()); + let fields = if is_tuple { + quote! { ( #(#field_idents),* ) } + } else { + quote! { { #(#field_idents),* } } + }; - quote! { - Self::#v_ident { #(#fields),* } => { - ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); - #(#write_fields)* + quote! { + Self::#v_ident #fields => { + ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); + #(#write_fields)* + } } - } - }); + }) + .collect(); + if attr.non_exhaustive.is_some() { + write_match_arms.push(quote! { + _ => panic!("Unexpected variant in non-exhaustive enum"), + }) + } let write_impl = quote! { match obj { #(#write_match_arms)* } }; @@ -89,10 +166,17 @@ fn enum_or_error_ffi_converter_impl( let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { let idx = Index::from(i + 1); let v_ident = &v.ident; + let is_tuple = v.fields.iter().any(|f| f.ident.is_none()); let try_read_fields = v.fields.iter().map(try_read_field); - quote! { - #idx => Self::#v_ident { #(#try_read_fields)* }, + if is_tuple { + quote! { + #idx => Self::#v_ident ( #(#try_read_fields)* ), + } + } else { + quote! { + #idx => Self::#v_ident { #(#try_read_fields)* }, + } } }); let error_format_string = format!("Invalid {ident} enum value: {{}}"); @@ -127,69 +211,161 @@ fn enum_or_error_ffi_converter_impl( } } -fn write_field(f: &Field) -> TokenStream { - let ident = &f.ident; - let ty = &f.ty; - - quote! { - <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf); - } -} - -pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result<TokenStream> { +pub(crate) fn enum_meta_static_var( + ident: &Ident, + docstring: String, + discr_type: Option<Ident>, + enum_: &DataEnum, + attr: &EnumAttr, +) -> syn::Result<TokenStream> { let name = ident_to_string(ident); let module_path = mod_path()?; + let non_exhaustive = attr.non_exhaustive.is_some(); let mut metadata_expr = quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM) .concat_str(#module_path) .concat_str(#name) + .concat_option_bool(None) // forced_flatness }; + metadata_expr.extend(match discr_type { + None => quote! { .concat_bool(false) }, + Some(t) => quote! { .concat_bool(true).concat(<#t as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) } + }); metadata_expr.extend(variant_metadata(enum_)?); + metadata_expr.extend(quote! { + .concat_bool(#non_exhaustive) + .concat_long_str(#docstring) + }); Ok(create_metadata_items("enum", &name, metadata_expr, None)) } +fn variant_value(v: &Variant) -> syn::Result<TokenStream> { + let Some((_, e)) = &v.discriminant else { + return Ok(quote! { .concat_bool(false) }); + }; + // Attempting to expose an enum value which we don't understand is a hard-error + // rather than silently ignoring it. If we had the ability to emit a warning that + // might make more sense. + + // We can't sanely handle most expressions other than literals, but we can handle + // negative literals. + let mut negate = false; + let lit = match e { + Expr::Lit(lit) => lit, + Expr::Unary(expr_unary) if matches!(expr_unary.op, syn::UnOp::Neg(_)) => { + negate = true; + match *expr_unary.expr { + Expr::Lit(ref lit) => lit, + _ => { + return Err(syn::Error::new_spanned( + e, + "UniFFI disciminant values must be a literal", + )); + } + } + } + _ => { + return Err(syn::Error::new_spanned( + e, + "UniFFI disciminant values must be a literal", + )); + } + }; + let Lit::Int(ref intlit) = lit.lit else { + return Err(syn::Error::new_spanned( + v, + "UniFFI disciminant values must be a literal integer", + )); + }; + if !intlit.suffix().is_empty() { + return Err(syn::Error::new_spanned( + intlit, + "integer literals with suffix not supported by UniFFI here", + )); + } + let digits = if negate { + format!("-{}", intlit.base10_digits()) + } else { + intlit.base10_digits().to_string() + }; + Ok(quote! { + .concat_bool(true) + .concat_value(::uniffi::metadata::codes::LIT_INT) + .concat_str(#digits) + }) +} + pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> { let variants_len = try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; std::iter::once(Ok(quote! { .concat_value(#variants_len) })) - .chain( - enum_.variants + .chain(enum_.variants.iter().map(|v| { + let fields_len = try_metadata_value_from_usize( + v.fields.len(), + "UniFFI limits enum variants to 256 fields", + )?; + + let field_names = v + .fields .iter() - .map(|v| { - let fields_len = try_metadata_value_from_usize( - v.fields.len(), - "UniFFI limits enum variants to 256 fields", - )?; - - let field_names = v.fields - .iter() - .map(|f| { - f.ident - .as_ref() - .ok_or_else(|| - syn::Error::new_spanned( - v, - "UniFFI only supports enum variants with named fields (or no fields at all)", - ) - ) - .map(ident_to_string) - }) - .collect::<syn::Result<Vec<_>>>()?; - - let name = ident_to_string(&v.ident); - let field_types = v.fields.iter().map(|f| &f.ty); - Ok(quote! { - .concat_str(#name) - .concat_value(#fields_len) - #( - .concat_str(#field_names) - .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) - // field defaults not yet supported for enums - .concat_bool(false) - )* - }) - }) - ) + .map(|f| f.ident.as_ref().map(ident_to_string).unwrap_or_default()) + .collect::<Vec<_>>(); + + let name = ident_to_string(&v.ident); + let value_tokens = variant_value(v)?; + let docstring = extract_docstring(&v.attrs)?; + let field_types = v.fields.iter().map(|f| &f.ty); + let field_docstrings = v + .fields + .iter() + .map(|f| extract_docstring(&f.attrs)) + .collect::<syn::Result<Vec<_>>>()?; + + Ok(quote! { + .concat_str(#name) + #value_tokens + .concat_value(#fields_len) + #( + .concat_str(#field_names) + .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) + // field defaults not yet supported for enums + .concat_bool(false) + .concat_long_str(#field_docstrings) + )* + .concat_long_str(#docstring) + }) + })) .collect() } + +#[derive(Default)] +pub struct EnumAttr { + pub non_exhaustive: Option<kw::non_exhaustive>, +} + +// So ErrorAttr can be used with `parse_macro_input!` +impl Parse for EnumAttr { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for EnumAttr { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::non_exhaustive) { + Ok(Self { + non_exhaustive: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?, + }) + } +} diff --git a/third_party/rust/uniffi_macros/src/error.rs b/third_party/rust/uniffi_macros/src/error.rs index a2ee7cf603..804b438003 100644 --- a/third_party/rust/uniffi_macros/src/error.rs +++ b/third_party/rust/uniffi_macros/src/error.rs @@ -6,11 +6,11 @@ use syn::{ }; use crate::{ - enum_::{rich_error_ffi_converter_impl, variant_metadata}, + enum_::{rich_error_ffi_converter_impl, variant_metadata, EnumAttr}, util::{ - chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, ident_to_string, kw, - mod_path, parse_comma_separated, tagged_impl_header, try_metadata_value_from_usize, - AttributeSliceExt, UniffiAttributeArgs, + chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, extract_docstring, + ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header, + try_metadata_value_from_usize, AttributeSliceExt, UniffiAttributeArgs, }, }; @@ -30,13 +30,14 @@ pub fn expand_error( } }; let ident = &input.ident; + let docstring = extract_docstring(&input.attrs)?; let mut attr: ErrorAttr = input.attrs.parse_uniffi_attr_args()?; if let Some(attr_from_udl_mode) = attr_from_udl_mode { attr = attr.merge(attr_from_udl_mode)?; } - let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode); + let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode)?; let meta_static_var = (!udl_mode).then(|| { - error_meta_static_var(ident, &enum_, attr.flat.is_some()) + error_meta_static_var(ident, docstring, &enum_, &attr) .unwrap_or_else(syn::Error::into_compile_error) }); @@ -67,23 +68,23 @@ fn error_ffi_converter_impl( enum_: &DataEnum, attr: &ErrorAttr, udl_mode: bool, -) -> TokenStream { - if attr.flat.is_some() { - flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr.with_try_read.is_some()) +) -> syn::Result<TokenStream> { + Ok(if attr.flat.is_some() { + flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr) } else { - rich_error_ffi_converter_impl(ident, enum_, udl_mode) - } + rich_error_ffi_converter_impl(ident, enum_, udl_mode, &attr.clone().try_into()?) + }) } // FfiConverters for "flat errors" // -// These are errors where we only lower the to_string() value, rather than any assocated data. +// These are errors where we only lower the to_string() value, rather than any associated data. // We lower the to_string() value unconditionally, whether the enum has associated data or not. fn flat_error_ffi_converter_impl( ident: &Ident, enum_: &DataEnum, udl_mode: bool, - implement_lift: bool, + attr: &ErrorAttr, ) -> TokenStream { let name = ident_to_string(ident); let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode); @@ -95,7 +96,7 @@ fn flat_error_ffi_converter_impl( }; let lower_impl = { - let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let mut match_arms: Vec<_> = enum_.variants.iter().enumerate().map(|(i, v)| { let v_ident = &v.ident; let idx = Index::from(i + 1); @@ -105,7 +106,12 @@ fn flat_error_ffi_converter_impl( <::std::string::String as ::uniffi::Lower<crate::UniFfiTag>>::write(error_msg, buf); } } - }); + }).collect(); + if attr.non_exhaustive.is_some() { + match_arms.push(quote! { + _ => panic!("Unexpected variant in non-exhaustive enum"), + }) + } quote! { #[automatically_derived] @@ -128,7 +134,7 @@ fn flat_error_ffi_converter_impl( } }; - let lift_impl = if implement_lift { + let lift_impl = if attr.with_try_read.is_some() { let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { let v_ident = &v.ident; let idx = Index::from(i + 1); @@ -192,42 +198,53 @@ fn flat_error_ffi_converter_impl( pub(crate) fn error_meta_static_var( ident: &Ident, + docstring: String, enum_: &DataEnum, - flat: bool, + attr: &ErrorAttr, ) -> syn::Result<TokenStream> { let name = ident_to_string(ident); let module_path = mod_path()?; + let flat = attr.flat.is_some(); + let non_exhaustive = attr.non_exhaustive.is_some(); let mut metadata_expr = quote! { - ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ERROR) - // first our is-flat flag - .concat_bool(#flat) - // followed by an enum + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM) .concat_str(#module_path) .concat_str(#name) + .concat_option_bool(Some(#flat)) + .concat_bool(false) // discr_type: None }; if flat { metadata_expr.extend(flat_error_variant_metadata(enum_)?) } else { metadata_expr.extend(variant_metadata(enum_)?); } + metadata_expr.extend(quote! { + .concat_bool(#non_exhaustive) + .concat_long_str(#docstring) + }); Ok(create_metadata_items("error", &name, metadata_expr, None)) } pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> { let variants_len = try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; - Ok(std::iter::once(quote! { .concat_value(#variants_len) }) + std::iter::once(Ok(quote! { .concat_value(#variants_len) })) .chain(enum_.variants.iter().map(|v| { let name = ident_to_string(&v.ident); - quote! { .concat_str(#name) } + let docstring = extract_docstring(&v.attrs)?; + Ok(quote! { + .concat_str(#name) + .concat_long_str(#docstring) + }) })) - .collect()) + .collect() } -#[derive(Default)] +#[derive(Clone, Default)] pub struct ErrorAttr { - flat: Option<kw::flat_error>, - with_try_read: Option<kw::with_try_read>, + pub flat: Option<kw::flat_error>, + pub with_try_read: Option<kw::with_try_read>, + pub non_exhaustive: Option<kw::non_exhaustive>, } impl UniffiAttributeArgs for ErrorAttr { @@ -243,8 +260,13 @@ impl UniffiAttributeArgs for ErrorAttr { with_try_read: input.parse()?, ..Self::default() }) + } else if lookahead.peek(kw::non_exhaustive) { + Ok(Self { + non_exhaustive: input.parse()?, + ..Self::default() + }) } else if lookahead.peek(kw::handle_unknown_callback_error) { - // Not used anymore, but still lallowed + // Not used anymore, but still allowed Ok(Self::default()) } else { Err(lookahead.error()) @@ -255,6 +277,7 @@ impl UniffiAttributeArgs for ErrorAttr { Ok(Self { flat: either_attribute_arg(self.flat, other.flat)?, with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?, + non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?, }) } } @@ -265,3 +288,25 @@ impl Parse for ErrorAttr { parse_comma_separated(input) } } + +impl TryFrom<ErrorAttr> for EnumAttr { + type Error = syn::Error; + + fn try_from(error_attr: ErrorAttr) -> Result<Self, Self::Error> { + if error_attr.flat.is_some() { + Err(syn::Error::new( + Span::call_site(), + "flat attribute not valid for rich enum errors", + )) + } else if error_attr.with_try_read.is_some() { + Err(syn::Error::new( + Span::call_site(), + "with_try_read attribute not valid for rich enum errors", + )) + } else { + Ok(EnumAttr { + non_exhaustive: error_attr.non_exhaustive, + }) + } + } +} diff --git a/third_party/rust/uniffi_macros/src/export.rs b/third_party/rust/uniffi_macros/src/export.rs index bbb16acf90..41657a639e 100644 --- a/third_party/rust/uniffi_macros/src/export.rs +++ b/third_party/rust/uniffi_macros/src/export.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{visit_mut::VisitMut, Item, Type}; @@ -10,6 +10,7 @@ mod attributes; mod callback_interface; mod item; mod scaffolding; +mod trait_interface; mod utrait; use self::{ @@ -18,20 +19,16 @@ use self::{ gen_constructor_scaffolding, gen_ffi_function, gen_fn_scaffolding, gen_method_scaffolding, }, }; -use crate::{ - object::interface_meta_static_var, - util::{ident_to_string, mod_path, tagged_impl_header}, -}; -pub use attributes::ExportAttributeArguments; +use crate::util::{ident_to_string, mod_path}; +pub use attributes::{DefaultMap, ExportFnArgs, ExportedImplFnArgs}; pub use callback_interface::ffi_converter_callback_interface_impl; -use uniffi_meta::free_fn_symbol_name; // TODO(jplatte): Ensure no generics, … // TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible pub(crate) fn expand_export( mut item: Item, - args: ExportAttributeArguments, + all_args: proc_macro::TokenStream, udl_mode: bool, ) -> syn::Result<TokenStream> { let mod_path = mod_path()?; @@ -41,11 +38,17 @@ pub(crate) fn expand_export( // new functions outside of the `impl`). rewrite_self_type(&mut item); - let metadata = ExportItem::new(item, &args)?; + let metadata = ExportItem::new(item, all_args)?; match metadata { - ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args, udl_mode), - ExportItem::Impl { items, self_ident } => { + ExportItem::Function { sig, args } => { + gen_fn_scaffolding(sig, &args.async_runtime, udl_mode) + } + ExportItem::Impl { + items, + self_ident, + args, + } => { if let Some(rt) = &args.async_runtime { if items .iter() @@ -61,8 +64,12 @@ pub(crate) fn expand_export( let item_tokens: TokenStream = items .into_iter() .map(|item| match item { - ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args, udl_mode), - ImplItem::Method(sig) => gen_method_scaffolding(sig, &args, udl_mode), + ImplItem::Constructor(sig) => { + gen_constructor_scaffolding(sig, &args.async_runtime, udl_mode) + } + ImplItem::Method(sig) => { + gen_method_scaffolding(sig, &args.async_runtime, udl_mode) + } }) .collect::<syn::Result<_>>()?; Ok(quote_spanned! { self_ident.span() => #item_tokens }) @@ -70,111 +77,51 @@ pub(crate) fn expand_export( ExportItem::Trait { items, self_ident, - callback_interface: false, - } => { - if let Some(rt) = args.async_runtime { - return Err(syn::Error::new_spanned(rt, "not supported for traits")); - } - - let name = ident_to_string(&self_ident); - let free_fn_ident = - Ident::new(&free_fn_symbol_name(&mod_path, &name), Span::call_site()); - - let free_tokens = quote! { - #[doc(hidden)] - #[no_mangle] - pub extern "C" fn #free_fn_ident( - ptr: *const ::std::ffi::c_void, - call_status: &mut ::uniffi::RustCallStatus - ) { - uniffi::rust_call(call_status, || { - assert!(!ptr.is_null()); - drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) }); - Ok(()) - }); - } - }; - - let impl_tokens: TokenStream = items - .into_iter() - .map(|item| match item { - ImplItem::Method(sig) => { - if sig.is_async { - return Err(syn::Error::new( - sig.span, - "async trait methods are not supported", - )); - } - gen_method_scaffolding(sig, &args, udl_mode) - } - _ => unreachable!("traits have no constructors"), - }) - .collect::<syn::Result<_>>()?; - - let meta_static_var = (!udl_mode).then(|| { - interface_meta_static_var(&self_ident, true, &mod_path) - .unwrap_or_else(syn::Error::into_compile_error) - }); - let ffi_converter_tokens = ffi_converter_trait_impl(&self_ident, false); - - Ok(quote_spanned! { self_ident.span() => - #meta_static_var - #free_tokens - #ffi_converter_tokens - #impl_tokens - }) - } + with_foreign, + callback_interface_only: false, + docstring, + args, + } => trait_interface::gen_trait_scaffolding( + &mod_path, + args, + self_ident, + items, + udl_mode, + with_foreign, + docstring, + ), ExportItem::Trait { items, self_ident, - callback_interface: true, + callback_interface_only: true, + docstring, + .. } => { let trait_name = ident_to_string(&self_ident); - let trait_impl_ident = Ident::new( - &format!("UniFFICallbackHandler{trait_name}"), - Span::call_site(), - ); - let internals_ident = Ident::new( - &format!( - "UNIFFI_FOREIGN_CALLBACK_INTERNALS_{}", - trait_name.to_ascii_uppercase() - ), - Span::call_site(), - ); - - let trait_impl = callback_interface::trait_impl( - &trait_impl_ident, - &self_ident, - &internals_ident, - &items, - ) - .unwrap_or_else(|e| e.into_compile_error()); - let metadata_items = callback_interface::metadata_items(&self_ident, &items, &mod_path) - .unwrap_or_else(|e| vec![e.into_compile_error()]); - - let init_ident = Ident::new( - &uniffi_meta::init_callback_fn_symbol_name(&mod_path, &trait_name), - Span::call_site(), - ); + let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); + let trait_impl = callback_interface::trait_impl(&mod_path, &self_ident, &items) + .unwrap_or_else(|e| e.into_compile_error()); + let metadata_items = (!udl_mode).then(|| { + let items = + callback_interface::metadata_items(&self_ident, &items, &mod_path, docstring) + .unwrap_or_else(|e| vec![e.into_compile_error()]); + quote! { #(#items)* } + }); + let ffi_converter_tokens = + ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident, udl_mode); Ok(quote! { - #[doc(hidden)] - static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new(); - - #[doc(hidden)] - #[no_mangle] - pub extern "C" fn #init_ident(callback: ::uniffi::ForeignCallback, _: &mut ::uniffi::RustCallStatus) { - #internals_ident.set_callback(callback); - } - #trait_impl - #(#metadata_items)* + #ffi_converter_tokens + + #metadata_items }) } ExportItem::Struct { self_ident, uniffi_traits, + .. } => { assert!(!udl_mode); utrait::expand_uniffi_trait_export(self_ident, uniffi_traits) @@ -182,62 +129,6 @@ pub(crate) fn expand_export( } } -pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, udl_mode: bool) -> TokenStream { - let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); - let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); - let name = ident_to_string(trait_ident); - let mod_path = match mod_path() { - Ok(p) => p, - Err(e) => return e.into_compile_error(), - }; - - quote! { - // All traits must be `Sync + Send`. The generated scaffolding will fail to compile - // if they are not, but unfortunately it fails with an unactionably obscure error message. - // By asserting the requirement explicitly, we help Rust produce a more scrutable error message - // and thus help the user debug why the requirement isn't being met. - uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: Sync, Send); - - unsafe #impl_spec { - type FfiType = *const ::std::os::raw::c_void; - - fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType { - ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void - } - - fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> { - let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(v as *mut ::std::sync::Arc<Self>) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(foreign_arc)) - } - - fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::deps::bytes::BufMut::put_u64( - buf, - <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64, - ); - } - - fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::check_remaining(buf, 8)?; - <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift( - ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) - } - - const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) - .concat_str(#mod_path) - .concat_str(#name) - .concat_bool(true); - } - - unsafe #lift_ref_impl_spec { - type LiftType = ::std::sync::Arc<dyn #trait_ident>; - } - } -} - /// Rewrite Self type alias usage in an impl block to the type itself. /// /// For example, diff --git a/third_party/rust/uniffi_macros/src/export/attributes.rs b/third_party/rust/uniffi_macros/src/export/attributes.rs index c3edcd5920..be7e8902e4 100644 --- a/third_party/rust/uniffi_macros/src/export/attributes.rs +++ b/third_party/rust/uniffi_macros/src/export/attributes.rs @@ -1,31 +1,33 @@ -use crate::util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}; +use std::collections::{HashMap, HashSet}; + +use crate::{ + default::DefaultValue, + util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}, +}; use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ + parenthesized, parse::{Parse, ParseStream}, - Attribute, LitStr, Meta, PathArguments, PathSegment, Token, + Attribute, Ident, LitStr, Meta, PathArguments, PathSegment, Token, }; +use uniffi_meta::UniffiTraitDiscriminants; #[derive(Default)] -pub struct ExportAttributeArguments { +pub struct ExportTraitArgs { pub(crate) async_runtime: Option<AsyncRuntime>, pub(crate) callback_interface: Option<kw::callback_interface>, - pub(crate) constructor: Option<kw::constructor>, - // tried to make this a vec but that got messy quickly... - pub(crate) trait_debug: Option<kw::Debug>, - pub(crate) trait_display: Option<kw::Display>, - pub(crate) trait_hash: Option<kw::Hash>, - pub(crate) trait_eq: Option<kw::Eq>, + pub(crate) with_foreign: Option<kw::with_foreign>, } -impl Parse for ExportAttributeArguments { +impl Parse for ExportTraitArgs { fn parse(input: ParseStream<'_>) -> syn::Result<Self> { parse_comma_separated(input) } } -impl UniffiAttributeArgs for ExportAttributeArguments { +impl UniffiAttributeArgs for ExportTraitArgs { fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { let lookahead = input.lookahead1(); if lookahead.peek(kw::async_runtime) { @@ -40,52 +42,175 @@ impl UniffiAttributeArgs for ExportAttributeArguments { callback_interface: input.parse()?, ..Self::default() }) - } else if lookahead.peek(kw::constructor) { + } else if lookahead.peek(kw::with_foreign) { Ok(Self { - constructor: input.parse()?, + with_foreign: input.parse()?, ..Self::default() }) - } else if lookahead.peek(kw::Debug) { + } else { + Ok(Self::default()) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + let merged = Self { + async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, + callback_interface: either_attribute_arg( + self.callback_interface, + other.callback_interface, + )?, + with_foreign: either_attribute_arg(self.with_foreign, other.with_foreign)?, + }; + if merged.callback_interface.is_some() && merged.with_foreign.is_some() { + return Err(syn::Error::new( + merged.callback_interface.unwrap().span, + "`callback_interface` and `with_foreign` are mutually exclusive", + )); + } + Ok(merged) + } +} + +#[derive(Clone, Default)] +pub struct ExportFnArgs { + pub(crate) async_runtime: Option<AsyncRuntime>, + pub(crate) name: Option<String>, + pub(crate) defaults: DefaultMap, +} + +impl Parse for ExportFnArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportFnArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::async_runtime) { + let _: kw::async_runtime = input.parse()?; + let _: Token![=] = input.parse()?; Ok(Self { - trait_debug: input.parse()?, + async_runtime: Some(input.parse()?), ..Self::default() }) - } else if lookahead.peek(kw::Display) { + } else if lookahead.peek(kw::name) { + let _: kw::name = input.parse()?; + let _: Token![=] = input.parse()?; + let name = Some(input.parse::<LitStr>()?.value()); Ok(Self { - trait_display: input.parse()?, + name, ..Self::default() }) - } else if lookahead.peek(kw::Hash) { + } else if lookahead.peek(kw::default) { Ok(Self { - trait_hash: input.parse()?, + defaults: DefaultMap::parse(input)?, ..Self::default() }) - } else if lookahead.peek(kw::Eq) { + } else { + Err(syn::Error::new( + input.span(), + format!("uniffi::export attribute `{input}` is not supported here."), + )) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, + name: either_attribute_arg(self.name, other.name)?, + defaults: self.defaults.merge(other.defaults), + }) + } +} + +#[derive(Default)] +pub struct ExportImplArgs { + pub(crate) async_runtime: Option<AsyncRuntime>, +} + +impl Parse for ExportImplArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportImplArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::async_runtime) { + let _: kw::async_runtime = input.parse()?; + let _: Token![=] = input.parse()?; Ok(Self { - trait_eq: input.parse()?, - ..Self::default() + async_runtime: Some(input.parse()?), }) } else { - Ok(Self::default()) + Err(syn::Error::new( + input.span(), + format!("uniffi::export attribute `{input}` is not supported here."), + )) } } fn merge(self, other: Self) -> syn::Result<Self> { Ok(Self { async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, - callback_interface: either_attribute_arg( - self.callback_interface, - other.callback_interface, - )?, - constructor: either_attribute_arg(self.constructor, other.constructor)?, - trait_debug: either_attribute_arg(self.trait_debug, other.trait_debug)?, - trait_display: either_attribute_arg(self.trait_display, other.trait_display)?, - trait_hash: either_attribute_arg(self.trait_hash, other.trait_hash)?, - trait_eq: either_attribute_arg(self.trait_eq, other.trait_eq)?, }) } } +#[derive(Default)] +pub struct ExportStructArgs { + pub(crate) traits: HashSet<UniffiTraitDiscriminants>, +} + +impl Parse for ExportStructArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportStructArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::Debug) { + input.parse::<Option<kw::Debug>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Debug]), + }) + } else if lookahead.peek(kw::Display) { + input.parse::<Option<kw::Display>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Display]), + }) + } else if lookahead.peek(kw::Hash) { + input.parse::<Option<kw::Hash>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Hash]), + }) + } else if lookahead.peek(kw::Eq) { + input.parse::<Option<kw::Eq>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Eq]), + }) + } else { + Err(syn::Error::new( + input.span(), + format!( + "uniffi::export struct attributes must be builtin trait names; `{input}` is invalid" + ), + )) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + let mut traits = self.traits; + traits.extend(other.traits); + Ok(Self { traits }) + } +} + +#[derive(Clone)] pub(crate) enum AsyncRuntime { Tokio(LitStr), } @@ -111,9 +236,57 @@ impl ToTokens for AsyncRuntime { } } +/// Arguments for function inside an impl block +/// +/// This stores the parsed arguments for `uniffi::constructor` and `uniffi::method` +#[derive(Clone, Default)] +pub struct ExportedImplFnArgs { + pub(crate) name: Option<String>, + pub(crate) defaults: DefaultMap, +} + +impl Parse for ExportedImplFnArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportedImplFnArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::name) { + let _: kw::name = input.parse()?; + let _: Token![=] = input.parse()?; + let name = Some(input.parse::<LitStr>()?.value()); + Ok(Self { + name, + ..Self::default() + }) + } else if lookahead.peek(kw::default) { + Ok(Self { + defaults: DefaultMap::parse(input)?, + ..Self::default() + }) + } else { + Err(syn::Error::new( + input.span(), + format!("uniffi::constructor/method attribute `{input}` is not supported here."), + )) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + name: either_attribute_arg(self.name, other.name)?, + defaults: self.defaults.merge(other.defaults), + }) + } +} + #[derive(Default)] pub(super) struct ExportedImplFnAttributes { pub constructor: bool, + pub args: ExportedImplFnArgs, } impl ExportedImplFnAttributes { @@ -130,12 +303,11 @@ impl ExportedImplFnAttributes { } ensure_no_path_args(fst)?; - if let Meta::List(_) | Meta::NameValue(_) = &attr.meta { - return Err(syn::Error::new_spanned( - &attr.meta, - "attribute arguments are not currently recognized in this position", - )); - } + let args = match &attr.meta { + Meta::List(_) => attr.parse_args::<ExportedImplFnArgs>()?, + _ => Default::default(), + }; + this.args = args; if segs.len() != 2 { return Err(syn::Error::new_spanned( @@ -156,6 +328,14 @@ impl ExportedImplFnAttributes { } this.constructor = true; } + "method" => { + if this.constructor { + return Err(syn::Error::new_spanned( + attr, + "confused constructor/method attributes", + )); + } + } _ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")), } } @@ -171,3 +351,53 @@ fn ensure_no_path_args(seg: &PathSegment) -> syn::Result<()> { Err(syn::Error::new_spanned(&seg.arguments, "unexpected syntax")) } } + +/// Maps arguments to defaults for functions +#[derive(Clone, Default)] +pub struct DefaultMap { + map: HashMap<Ident, DefaultValue>, +} + +impl DefaultMap { + pub fn merge(self, other: Self) -> Self { + let mut map = self.map; + map.extend(other.map); + Self { map } + } + + pub fn remove(&mut self, ident: &Ident) -> Option<DefaultValue> { + self.map.remove(ident) + } + + pub fn idents(&self) -> Vec<&Ident> { + self.map.keys().collect() + } +} + +impl Parse for DefaultMap { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _: kw::default = input.parse()?; + let content; + let _ = parenthesized!(content in input); + let pairs = content.parse_terminated(DefaultPair::parse, Token![,])?; + Ok(Self { + map: pairs.into_iter().map(|p| (p.name, p.value)).collect(), + }) + } +} + +pub struct DefaultPair { + pub name: Ident, + pub eq_token: Token![=], + pub value: DefaultValue, +} + +impl Parse for DefaultPair { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + Ok(Self { + name: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } +} diff --git a/third_party/rust/uniffi_macros/src/export/callback_interface.rs b/third_party/rust/uniffi_macros/src/export/callback_interface.rs index 2f2561bbc2..fe145384ec 100644 --- a/third_party/rust/uniffi_macros/src/export/callback_interface.rs +++ b/third_party/rust/uniffi_macros/src/export/callback_interface.rs @@ -5,69 +5,137 @@ use crate::{ export::ImplItem, fnsig::{FnKind, FnSignature, ReceiverArg}, - util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}, + util::{ + create_metadata_items, derive_ffi_traits, ident_to_string, mod_path, tagged_impl_header, + }, }; use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{format_ident, quote}; use std::iter; use syn::Ident; +/// Generate a trait impl that calls foreign callbacks +/// +/// This generates: +/// * A `repr(C)` VTable struct where each field is the FFI function for the trait method. +/// * A FFI function for foreign code to set their VTable for the interface +/// * An implementation of the trait using that VTable pub(super) fn trait_impl( - ident: &Ident, + mod_path: &str, trait_ident: &Ident, - internals_ident: &Ident, items: &[ImplItem], ) -> syn::Result<TokenStream> { - let trait_impl_methods = items + let trait_name = ident_to_string(trait_ident); + let trait_impl_ident = trait_impl_ident(&trait_name); + let vtable_type = format_ident!("UniFfiTraitVtable{trait_name}"); + let vtable_cell = format_ident!("UNIFFI_TRAIT_CELL_{}", trait_name.to_uppercase()); + let init_ident = Ident::new( + &uniffi_meta::init_callback_vtable_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); + let methods = items .iter() .map(|item| match item { - ImplItem::Method(sig) => gen_method_impl(sig, internals_ident), - _ => unreachable!("traits have no constructors"), + ImplItem::Constructor(sig) => Err(syn::Error::new( + sig.span, + "Constructors not allowed in trait interfaces", + )), + ImplItem::Method(sig) => Ok(sig), }) - .collect::<syn::Result<TokenStream>>()?; - let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, false); + .collect::<syn::Result<Vec<_>>>()?; + + let vtable_fields = methods.iter() + .map(|sig| { + let ident = &sig.ident; + let param_names = sig.scaffolding_param_names(); + let param_types = sig.scaffolding_param_types(); + let lift_return = sig.lift_return_impl(); + if !sig.is_async { + quote! { + #ident: extern "C" fn( + uniffi_handle: u64, + #(#param_names: #param_types,)* + uniffi_out_return: &mut #lift_return::ReturnType, + uniffi_out_call_status: &mut ::uniffi::RustCallStatus, + ), + } + } else { + quote! { + #ident: extern "C" fn( + uniffi_handle: u64, + #(#param_names: #param_types,)* + uniffi_future_callback: ::uniffi::ForeignFutureCallback<#lift_return::ReturnType>, + uniffi_callback_data: u64, + uniffi_out_return: &mut ::uniffi::ForeignFuture, + ), + } + } + }); + + let trait_impl_methods = methods + .iter() + .map(|sig| gen_method_impl(sig, &vtable_cell)) + .collect::<syn::Result<Vec<_>>>()?; + let has_async_method = methods.iter().any(|m| m.is_async); + let impl_attributes = has_async_method.then(|| quote! { #[::async_trait::async_trait] }); Ok(quote! { - #[doc(hidden)] + struct #vtable_type { + #(#vtable_fields)* + uniffi_free: extern "C" fn(handle: u64), + } + + static #vtable_cell: ::uniffi::UniffiForeignPointerCell::<#vtable_type> = ::uniffi::UniffiForeignPointerCell::<#vtable_type>::new(); + + #[no_mangle] + extern "C" fn #init_ident(vtable: ::std::ptr::NonNull<#vtable_type>) { + #vtable_cell.set(vtable); + } + #[derive(Debug)] - struct #ident { + struct #trait_impl_ident { handle: u64, } - impl #ident { + impl #trait_impl_ident { fn new(handle: u64) -> Self { Self { handle } } } - impl ::std::ops::Drop for #ident { - fn drop(&mut self) { - #internals_ident.invoke_callback::<(), crate::UniFfiTag>( - self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() - ) - } - } - - ::uniffi::deps::static_assertions::assert_impl_all!(#ident: Send); + ::uniffi::deps::static_assertions::assert_impl_all!(#trait_impl_ident: ::core::marker::Send); - impl #trait_ident for #ident { - #trait_impl_methods + #impl_attributes + impl #trait_ident for #trait_impl_ident { + #(#trait_impl_methods)* } - #ffi_converter_tokens + impl ::std::ops::Drop for #trait_impl_ident { + fn drop(&mut self) { + let vtable = #vtable_cell.get(); + (vtable.uniffi_free)(self.handle); + } + } }) } +pub fn trait_impl_ident(trait_name: &str) -> Ident { + Ident::new( + &format!("UniFFICallbackHandler{trait_name}"), + Span::call_site(), + ) +} + pub fn ffi_converter_callback_interface_impl( trait_ident: &Ident, trait_impl_ident: &Ident, udl_mode: bool, ) -> TokenStream { - let name = ident_to_string(trait_ident); + let trait_name = ident_to_string(trait_ident); let dyn_trait = quote! { dyn #trait_ident }; let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> }; let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, udl_mode); - let lift_ref_impl_spec = tagged_impl_header("LiftRef", &dyn_trait, udl_mode); + let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, udl_mode, &["LiftRef", "LiftReturn"]); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -93,66 +161,85 @@ pub fn ffi_converter_callback_interface_impl( ::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE, ) .concat_str(#mod_path) - .concat_str(#name); + .concat_str(#trait_name); } - unsafe #lift_ref_impl_spec { - type LiftType = #box_dyn_trait; - } + #derive_ffi_traits } } -fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result<TokenStream> { +/// Generate a single method for [trait_impl]. This implements a trait method by invoking a +/// foreign-supplied callback. +fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result<TokenStream> { let FnSignature { ident, + is_async, return_ty, kind, receiver, + name, + span, .. } = sig; - let index = match kind { - // Note: the callback index is 1-based, since 0 is reserved for the free function - FnKind::TraitMethod { index, .. } => index + 1, - k => { - return Err(syn::Error::new( - sig.span, - format!( - "Internal UniFFI error: Unexpected function kind for callback interface {k:?}" - ), - )); - } - }; + + if !matches!(kind, FnKind::TraitMethod { .. }) { + return Err(syn::Error::new( + *span, + format!( + "Internal UniFFI error: Unexpected function kind for callback interface {name}: {kind:?}", + ), + )); + } let self_param = match receiver { + Some(ReceiverArg::Ref) => quote! { &self }, + Some(ReceiverArg::Arc) => quote! { self: Arc<Self> }, None => { return Err(syn::Error::new( - sig.span, + *span, "callback interface methods must take &self as their first argument", )); } - Some(ReceiverArg::Ref) => quote! { &self }, - Some(ReceiverArg::Arc) => quote! { self: Arc<Self> }, }; + let params = sig.params(); - let buf_ident = Ident::new("uniffi_args_buf", Span::call_site()); - let write_exprs = sig.write_exprs(&buf_ident); + let lower_exprs = sig.args.iter().map(|a| { + let lower_impl = a.lower_impl(); + let ident = &a.ident; + quote! { #lower_impl::lower(#ident) } + }); - Ok(quote! { - fn #ident(#self_param, #(#params),*) -> #return_ty { - #[allow(unused_mut)] - let mut #buf_ident = ::std::vec::Vec::new(); - #(#write_exprs;)* - let uniffi_args_rbuf = uniffi::RustBuffer::from_vec(#buf_ident); + let lift_return = sig.lift_return_impl(); - #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf) - } - }) + if !is_async { + Ok(quote! { + fn #ident(#self_param, #(#params),*) -> #return_ty { + let vtable = #vtable_cell.get(); + let mut uniffi_call_status = ::uniffi::RustCallStatus::new(); + let mut uniffi_return_value: #lift_return::ReturnType = ::uniffi::FfiDefault::ffi_default(); + (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut uniffi_return_value, &mut uniffi_call_status); + #lift_return::lift_foreign_return(uniffi_return_value, uniffi_call_status) + } + }) + } else { + Ok(quote! { + async fn #ident(#self_param, #(#params),*) -> #return_ty { + let vtable = #vtable_cell.get(); + ::uniffi::foreign_async_call::<_, #return_ty, crate::UniFfiTag>(move |uniffi_future_callback, uniffi_future_callback_data| { + let mut uniffi_foreign_future: ::uniffi::ForeignFuture = ::uniffi::FfiDefault::ffi_default(); + (vtable.#ident)(self.handle, #(#lower_exprs,)* uniffi_future_callback, uniffi_future_callback_data, &mut uniffi_foreign_future); + uniffi_foreign_future + }).await + } + }) + } } pub(super) fn metadata_items( self_ident: &Ident, items: &[ImplItem], module_path: &str, + docstring: String, ) -> syn::Result<Vec<TokenStream>> { let trait_name = ident_to_string(self_ident); let callback_interface_items = create_metadata_items( @@ -162,13 +249,14 @@ pub(super) fn metadata_items( ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE) .concat_str(#module_path) .concat_str(#trait_name) + .concat_long_str(#docstring) }, None, ); iter::once(Ok(callback_interface_items)) .chain(items.iter().map(|item| match item { - ImplItem::Method(sig) => sig.metadata_items(), + ImplItem::Method(sig) => sig.metadata_items_for_callback_interface(), _ => unreachable!("traits have no constructors"), })) .collect() diff --git a/third_party/rust/uniffi_macros/src/export/item.rs b/third_party/rust/uniffi_macros/src/export/item.rs index 98c7d0ebe2..da3c9455c8 100644 --- a/third_party/rust/uniffi_macros/src/export/item.rs +++ b/third_party/rust/uniffi_macros/src/export/item.rs @@ -3,24 +3,34 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::fnsig::FnSignature; +use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::ToTokens; -use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes}; +use super::attributes::{ + ExportFnArgs, ExportImplArgs, ExportStructArgs, ExportTraitArgs, ExportedImplFnArgs, + ExportedImplFnAttributes, +}; +use crate::util::extract_docstring; use uniffi_meta::UniffiTraitDiscriminants; pub(super) enum ExportItem { Function { sig: FnSignature, + args: ExportFnArgs, }, Impl { self_ident: Ident, items: Vec<ImplItem>, + args: ExportImplArgs, }, Trait { self_ident: Ident, items: Vec<ImplItem>, - callback_interface: bool, + with_foreign: bool, + callback_interface_only: bool, + docstring: String, + args: ExportTraitArgs, }, Struct { self_ident: Ident, @@ -29,15 +39,17 @@ pub(super) enum ExportItem { } impl ExportItem { - pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result<Self> { + pub fn new(item: syn::Item, attr_args: TokenStream) -> syn::Result<Self> { match item { syn::Item::Fn(item) => { - let sig = FnSignature::new_function(item.sig)?; - Ok(Self::Function { sig }) + let args: ExportFnArgs = syn::parse(attr_args)?; + let docstring = extract_docstring(&item.attrs)?; + let sig = FnSignature::new_function(item.sig, args.clone(), docstring)?; + Ok(Self::Function { sig, args }) } - syn::Item::Impl(item) => Self::from_impl(item, args.constructor.is_some()), - syn::Item::Trait(item) => Self::from_trait(item, args.callback_interface.is_some()), - syn::Item::Struct(item) => Self::from_struct(item, args), + syn::Item::Impl(item) => Self::from_impl(item, attr_args), + syn::Item::Trait(item) => Self::from_trait(item, attr_args), + syn::Item::Struct(item) => Self::from_struct(item, attr_args), // FIXME: Support const / static? _ => Err(syn::Error::new( Span::call_site(), @@ -47,7 +59,8 @@ impl ExportItem { } } - pub fn from_impl(item: syn::ItemImpl, force_constructor: bool) -> syn::Result<Self> { + pub fn from_impl(item: syn::ItemImpl, attr_args: TokenStream) -> syn::Result<Self> { + let args: ExportImplArgs = syn::parse(attr_args)?; if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -88,14 +101,22 @@ impl ExportItem { } }; + let docstring = extract_docstring(&impl_fn.attrs)?; let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?; - let item = if force_constructor || attrs.constructor { + let item = if attrs.constructor { ImplItem::Constructor(FnSignature::new_constructor( self_ident.clone(), impl_fn.sig, + attrs.args, + docstring, )?) } else { - ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?) + ImplItem::Method(FnSignature::new_method( + self_ident.clone(), + impl_fn.sig, + attrs.args, + docstring, + )?) }; Ok(item) @@ -105,10 +126,15 @@ impl ExportItem { Ok(Self::Impl { items, self_ident: self_ident.to_owned(), + args, }) } - fn from_trait(item: syn::ItemTrait, callback_interface: bool) -> syn::Result<Self> { + fn from_trait(item: syn::ItemTrait, attr_args: TokenStream) -> syn::Result<Self> { + let args: ExportTraitArgs = syn::parse(attr_args)?; + let with_foreign = args.callback_interface.is_some() || args.with_foreign.is_some(); + let callback_interface_only = args.callback_interface.is_some(); + if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -117,6 +143,7 @@ impl ExportItem { } let self_ident = item.ident.to_owned(); + let docstring = extract_docstring(&item.attrs)?; let items = item .items .into_iter() @@ -132,6 +159,7 @@ impl ExportItem { } }; + let docstring = extract_docstring(&tim.attrs)?; let attrs = ExportedImplFnAttributes::new(&tim.attrs)?; let item = if attrs.constructor { return Err(syn::Error::new_spanned( @@ -142,7 +170,9 @@ impl ExportItem { ImplItem::Method(FnSignature::new_trait_method( self_ident.clone(), tim.sig, + ExportedImplFnArgs::default(), i as u32, + docstring, )?) }; @@ -153,28 +183,26 @@ impl ExportItem { Ok(Self::Trait { items, self_ident, - callback_interface, + with_foreign, + callback_interface_only, + docstring, + args, }) } - fn from_struct(item: syn::ItemStruct, args: &ExportAttributeArguments) -> syn::Result<Self> { - let mut uniffi_traits = Vec::new(); - if args.trait_debug.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Debug); - } - if args.trait_display.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Display); - } - if args.trait_hash.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Hash); - } - if args.trait_eq.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Eq); + fn from_struct(item: syn::ItemStruct, attr_args: TokenStream) -> syn::Result<Self> { + let args: ExportStructArgs = syn::parse(attr_args)?; + let uniffi_traits: Vec<UniffiTraitDiscriminants> = args.traits.into_iter().collect(); + if uniffi_traits.is_empty() { + Err(syn::Error::new(Span::call_site(), + "uniffi::export on a struct must supply a builtin trait name. Did you mean `#[derive(uniffi::Object)]`?" + )) + } else { + Ok(Self::Struct { + self_ident: item.ident, + uniffi_traits, + }) } - Ok(Self::Struct { - self_ident: item.ident, - uniffi_traits, - }) } } diff --git a/third_party/rust/uniffi_macros/src/export/scaffolding.rs b/third_party/rust/uniffi_macros/src/export/scaffolding.rs index f120ccc880..fa7b61deca 100644 --- a/third_party/rust/uniffi_macros/src/export/scaffolding.rs +++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs @@ -6,12 +6,12 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use std::iter; -use super::attributes::{AsyncRuntime, ExportAttributeArguments}; -use crate::fnsig::{FnKind, FnSignature, NamedArg}; +use super::attributes::AsyncRuntime; +use crate::fnsig::{FnKind, FnSignature}; pub(super) fn gen_fn_scaffolding( sig: FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { if sig.receiver.is_some() { @@ -21,7 +21,7 @@ pub(super) fn gen_fn_scaffolding( )); } if !sig.is_async { - if let Some(async_runtime) = &arguments.async_runtime { + if let Some(async_runtime) = ar { return Err(syn::Error::new_spanned( async_runtime, "this attribute is only allowed on async functions", @@ -32,7 +32,7 @@ pub(super) fn gen_fn_scaffolding( sig.metadata_items() .unwrap_or_else(syn::Error::into_compile_error) }); - let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?; + let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?; Ok(quote! { #scaffolding_func #metadata_items @@ -41,7 +41,7 @@ pub(super) fn gen_fn_scaffolding( pub(super) fn gen_constructor_scaffolding( sig: FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { if sig.receiver.is_some() { @@ -50,14 +50,11 @@ pub(super) fn gen_constructor_scaffolding( "constructors must not have a self parameter", )); } - if sig.is_async { - return Err(syn::Error::new(sig.span, "constructors can't be async")); - } let metadata_items = (!udl_mode).then(|| { sig.metadata_items() .unwrap_or_else(syn::Error::into_compile_error) }); - let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?; + let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?; Ok(quote! { #scaffolding_func #metadata_items @@ -66,7 +63,7 @@ pub(super) fn gen_constructor_scaffolding( pub(super) fn gen_method_scaffolding( sig: FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { let scaffolding_func = if sig.receiver.is_none() { @@ -75,7 +72,7 @@ pub(super) fn gen_method_scaffolding( "associated functions are not currently supported", )); } else { - gen_ffi_function(&sig, arguments, udl_mode)? + gen_ffi_function(&sig, ar, udl_mode)? }; let metadata_items = (!udl_mode).then(|| { @@ -90,31 +87,37 @@ pub(super) fn gen_method_scaffolding( // Pieces of code for the scaffolding function struct ScaffoldingBits { - /// Parameters for the scaffolding function - params: Vec<TokenStream>, + /// Parameter names for the scaffolding function + param_names: Vec<TokenStream>, + /// Parameter types for the scaffolding function + param_types: Vec<TokenStream>, /// Lift closure. See `FnSignature::lift_closure` for an explanation of this. lift_closure: TokenStream, /// Expression to call the Rust function after a successful lift. rust_fn_call: TokenStream, + /// Convert the result of `rust_fn_call`, stored in a variable named `uniffi_result` into its final value. + /// This is used to do things like error conversion / Arc wrapping + convert_result: TokenStream, } impl ScaffoldingBits { fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self { let ident = &sig.ident; - let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); let call_params = sig.rust_call_params(false); let rust_fn_call = quote! { #ident(#call_params) }; // UDL mode adds an extra conversion (#1749) - let rust_fn_call = if udl_mode && sig.looks_like_result { - quote! { #rust_fn_call.map_err(::std::convert::Into::into) } + let convert_result = if udl_mode && sig.looks_like_result { + quote! { uniffi_result.map_err(::std::convert::Into::into) } } else { - rust_fn_call + quote! { uniffi_result } }; Self { - params, + param_names: sig.scaffolding_param_names().collect(), + param_types: sig.scaffolding_param_types().collect(), lift_closure: sig.lift_closure(None), rust_fn_call, + convert_result, } } @@ -125,20 +128,32 @@ impl ScaffoldingBits { udl_mode: bool, ) -> Self { let ident = &sig.ident; - let ffi_converter = if is_trait { + let lift_impl = if is_trait { quote! { - <::std::sync::Arc<dyn #self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>> + <::std::sync::Arc<dyn #self_ident> as ::uniffi::Lift<crate::UniFfiTag>> } } else { quote! { - <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>> + <::std::sync::Arc<#self_ident> as ::uniffi::Lift<crate::UniFfiTag>> } }; - let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #ffi_converter::FfiType }) - .chain(sig.scaffolding_params()) - .collect(); + let try_lift_self = if is_trait { + // For trait interfaces we need to special case this. Trait interfaces normally lift + // foreign trait impl pointers. However, for a method call, we want to lift a Rust + // pointer. + quote! { + { + let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>) }; + // Take a clone for our own use. + Ok(*boxed_foreign_arc) + } + } + } else { + quote! { #lift_impl::try_lift(uniffi_self_lowered) } + }; + let lift_closure = sig.lift_closure(Some(quote! { - match #ffi_converter::try_lift(uniffi_self_lowered) { + match #try_lift_self { Ok(v) => v, Err(e) => return Err(("self", e)) } @@ -146,38 +161,45 @@ impl ScaffoldingBits { let call_params = sig.rust_call_params(true); let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) }; // UDL mode adds an extra conversion (#1749) - let rust_fn_call = if udl_mode && sig.looks_like_result { - quote! { #rust_fn_call.map_err(::std::convert::Into::into) } + let convert_result = if udl_mode && sig.looks_like_result { + quote! { uniffi_result .map_err(::std::convert::Into::into) } } else { - rust_fn_call + quote! { uniffi_result } }; Self { - params, + param_names: iter::once(quote! { uniffi_self_lowered }) + .chain(sig.scaffolding_param_names()) + .collect(), + param_types: iter::once(quote! { #lift_impl::FfiType }) + .chain(sig.scaffolding_param_types()) + .collect(), lift_closure, rust_fn_call, + convert_result, } } fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self { let ident = &sig.ident; - let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); let call_params = sig.rust_call_params(false); let rust_fn_call = quote! { #self_ident::#ident(#call_params) }; // UDL mode adds extra conversions (#1749) - let rust_fn_call = match (udl_mode, sig.looks_like_result) { + let convert_result = match (udl_mode, sig.looks_like_result) { // For UDL - (true, false) => quote! { ::std::sync::Arc::new(#rust_fn_call) }, + (true, false) => quote! { ::std::sync::Arc::new(uniffi_result) }, (true, true) => { - quote! { #rust_fn_call.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) } + quote! { uniffi_result.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) } } - (false, _) => rust_fn_call, + (false, _) => quote! { uniffi_result }, }; Self { - params, + param_names: sig.scaffolding_param_names().collect(), + param_types: sig.scaffolding_param_types().collect(), lift_closure: sig.lift_closure(None), rust_fn_call, + convert_result, } } } @@ -188,13 +210,15 @@ impl ScaffoldingBits { /// `rust_fn` is the Rust function to call. pub(super) fn gen_ffi_function( sig: &FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { let ScaffoldingBits { - params, + param_names, + param_types, lift_closure, rust_fn_call, + convert_result, } = match &sig.kind { FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode), FnKind::Method { self_ident } => { @@ -216,14 +240,15 @@ pub(super) fn gen_ffi_function( let ffi_ident = sig.scaffolding_fn_ident()?; let name = &sig.name; - let return_impl = &sig.return_impl(); + let return_ty = &sig.return_ty; + let return_impl = &sig.lower_return_impl(); Ok(if !sig.is_async { quote! { #[doc(hidden)] #[no_mangle] #vis extern "C" fn #ffi_ident( - #(#params,)* + #(#param_names: #param_types,)* call_status: &mut ::uniffi::RustCallStatus, ) -> #return_impl::ReturnType { ::uniffi::deps::log::debug!(#name); @@ -231,7 +256,10 @@ pub(super) fn gen_ffi_function( ::uniffi::rust_call(call_status, || { #return_impl::lower_return( match uniffi_lift_args() { - Ok(uniffi_args) => #rust_fn_call, + Ok(uniffi_args) => { + let uniffi_result = #rust_fn_call; + #convert_result + } Err((arg_name, anyhow_error)) => { #return_impl::handle_failed_lift(arg_name, anyhow_error) }, @@ -242,25 +270,28 @@ pub(super) fn gen_ffi_function( } } else { let mut future_expr = rust_fn_call; - if matches!(arguments.async_runtime, Some(AsyncRuntime::Tokio(_))) { + if matches!(ar, Some(AsyncRuntime::Tokio(_))) { future_expr = quote! { ::uniffi::deps::async_compat::Compat::new(#future_expr) } } quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle { + pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle { ::uniffi::deps::log::debug!(#name); let uniffi_lift_args = #lift_closure; match uniffi_lift_args() { Ok(uniffi_args) => { - ::uniffi::rust_future_new( - async move { #future_expr.await }, + ::uniffi::rust_future_new::<_, #return_ty, _>( + async move { + let uniffi_result = #future_expr.await; + #convert_result + }, crate::UniFfiTag ) }, Err((arg_name, anyhow_error)) => { - ::uniffi::rust_future_new( + ::uniffi::rust_future_new::<_, #return_ty, _>( async move { #return_impl::handle_failed_lift(arg_name, anyhow_error) }, diff --git a/third_party/rust/uniffi_macros/src/export/trait_interface.rs b/third_party/rust/uniffi_macros/src/export/trait_interface.rs new file mode 100644 index 0000000000..83587ae386 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/trait_interface.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, quote_spanned}; + +use uniffi_meta::ObjectImpl; + +use crate::{ + export::{ + attributes::ExportTraitArgs, callback_interface, gen_method_scaffolding, item::ImplItem, + }, + object::interface_meta_static_var, + util::{ident_to_string, tagged_impl_header}, +}; + +pub(super) fn gen_trait_scaffolding( + mod_path: &str, + args: ExportTraitArgs, + self_ident: Ident, + items: Vec<ImplItem>, + udl_mode: bool, + with_foreign: bool, + docstring: String, +) -> syn::Result<TokenStream> { + if let Some(rt) = args.async_runtime { + return Err(syn::Error::new_spanned(rt, "not supported for traits")); + } + let trait_name = ident_to_string(&self_ident); + let trait_impl = with_foreign.then(|| { + callback_interface::trait_impl(mod_path, &self_ident, &items) + .unwrap_or_else(|e| e.into_compile_error()) + }); + + let clone_fn_ident = Ident::new( + &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); + let free_fn_ident = Ident::new( + &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); + + let helper_fn_tokens = quote! { + #[doc(hidden)] + #[no_mangle] + /// Clone a pointer to this object type + /// + /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were + /// passed to the free function. + pub unsafe extern "C" fn #clone_fn_ident( + ptr: *const ::std::ffi::c_void, + call_status: &mut ::uniffi::RustCallStatus + ) -> *const ::std::ffi::c_void { + uniffi::rust_call(call_status, || { + let ptr = ptr as *mut std::sync::Arc<dyn #self_ident>; + let arc = unsafe { ::std::sync::Arc::clone(&*ptr) }; + Ok(::std::boxed::Box::into_raw(::std::boxed::Box::new(arc)) as *const ::std::ffi::c_void) + }) + } + + #[doc(hidden)] + #[no_mangle] + /// Free a pointer to this object type + /// + /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were + /// passed to the free function. + /// + /// Note: clippy doesn't complain about this being unsafe, but it definitely is since it + /// calls `Box::from_raw`. + pub unsafe extern "C" fn #free_fn_ident( + ptr: *const ::std::ffi::c_void, + call_status: &mut ::uniffi::RustCallStatus + ) { + uniffi::rust_call(call_status, || { + assert!(!ptr.is_null()); + drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) }); + Ok(()) + }); + } + }; + + let impl_tokens: TokenStream = items + .into_iter() + .map(|item| match item { + ImplItem::Method(sig) => gen_method_scaffolding(sig, &None, udl_mode), + _ => unreachable!("traits have no constructors"), + }) + .collect::<syn::Result<_>>()?; + + let meta_static_var = (!udl_mode).then(|| { + let imp = if with_foreign { + ObjectImpl::CallbackTrait + } else { + ObjectImpl::Trait + }; + interface_meta_static_var(&self_ident, imp, mod_path, docstring) + .unwrap_or_else(syn::Error::into_compile_error) + }); + let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign); + + Ok(quote_spanned! { self_ident.span() => + #meta_static_var + #helper_fn_tokens + #trait_impl + #impl_tokens + #ffi_converter_tokens + }) +} + +pub(crate) fn ffi_converter( + mod_path: &str, + trait_ident: &Ident, + udl_mode: bool, + with_foreign: bool, +) -> TokenStream { + let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); + let trait_name = ident_to_string(trait_ident); + let try_lift = if with_foreign { + let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); + quote! { + fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> { + Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64))) + } + } + } else { + quote! { + fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> { + unsafe { + Ok(*::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc<Self>)) + } + } + } + }; + let metadata_code = if with_foreign { + quote! { ::uniffi::metadata::codes::TYPE_CALLBACK_TRAIT_INTERFACE } + } else { + quote! { ::uniffi::metadata::codes::TYPE_TRAIT_INTERFACE } + }; + + quote! { + // All traits must be `Sync + Send`. The generated scaffolding will fail to compile + // if they are not, but unfortunately it fails with an unactionably obscure error message. + // By asserting the requirement explicitly, we help Rust produce a more scrutable error message + // and thus help the user debug why the requirement isn't being met. + uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send); + + unsafe #impl_spec { + type FfiType = *const ::std::os::raw::c_void; + + fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType { + ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void + } + + #try_lift + + fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) { + ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); + ::uniffi::deps::bytes::BufMut::put_u64( + buf, + <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64, + ); + } + + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> { + ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); + ::uniffi::check_remaining(buf, 8)?; + <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift( + ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_code) + .concat_str(#mod_path) + .concat_str(#trait_name); + } + + unsafe #lift_ref_impl_spec { + type LiftType = ::std::sync::Arc<dyn #trait_ident>; + } + } +} diff --git a/third_party/rust/uniffi_macros/src/export/utrait.rs b/third_party/rust/uniffi_macros/src/export/utrait.rs index 3db09ea2b7..9007ae2c56 100644 --- a/third_party/rust/uniffi_macros/src/export/utrait.rs +++ b/third_party/rust/uniffi_macros/src/export/utrait.rs @@ -6,8 +6,10 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::ext::IdentExt; -use super::{attributes::ExportAttributeArguments, gen_ffi_function}; +use super::gen_ffi_function; +use crate::export::ExportedImplFnArgs; use crate::fnsig::FnSignature; +use crate::util::extract_docstring; use uniffi_meta::UniffiTraitDiscriminants; pub(crate) fn expand_uniffi_trait_export( @@ -157,12 +159,25 @@ fn process_uniffi_trait_method( unreachable!() }; + let docstring = extract_docstring(&item.attrs)?; + let ffi_func = gen_ffi_function( - &FnSignature::new_method(self_ident.clone(), item.sig.clone())?, - &ExportAttributeArguments::default(), + &FnSignature::new_method( + self_ident.clone(), + item.sig.clone(), + ExportedImplFnArgs::default(), + docstring.clone(), + )?, + &None, udl_mode, )?; // metadata for the method, which will be packed inside metadata for the trait. - let method_meta = FnSignature::new_method(self_ident.clone(), item.sig)?.metadata_expr()?; + let method_meta = FnSignature::new_method( + self_ident.clone(), + item.sig, + ExportedImplFnArgs::default(), + docstring, + )? + .metadata_expr()?; Ok((ffi_func, method_meta)) } diff --git a/third_party/rust/uniffi_macros/src/fnsig.rs b/third_party/rust/uniffi_macros/src/fnsig.rs index 69b6529d1e..9c59125207 100644 --- a/third_party/rust/uniffi_macros/src/fnsig.rs +++ b/third_party/rust/uniffi_macros/src/fnsig.rs @@ -2,8 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::util::{ - create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize, +use crate::{ + default::{default_value_metadata_calls, DefaultValue}, + export::{DefaultMap, ExportFnArgs, ExportedImplFnArgs}, + util::{create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize}, }; use proc_macro2::{Span, TokenStream}; use quote::quote; @@ -13,7 +15,9 @@ pub(crate) struct FnSignature { pub kind: FnKind, pub span: Span, pub mod_path: String, + // The identifier of the Rust function. pub ident: Ident, + // The foreign name for this function, usually == ident. pub name: String, pub is_async: bool, pub receiver: Option<ReceiverArg>, @@ -23,30 +27,71 @@ pub(crate) struct FnSignature { // Only use this in UDL mode. // In general, it's not reliable because it fails for type aliases. pub looks_like_result: bool, + pub docstring: String, } impl FnSignature { - pub(crate) fn new_function(sig: syn::Signature) -> syn::Result<Self> { - Self::new(FnKind::Function, sig) + pub(crate) fn new_function( + sig: syn::Signature, + args: ExportFnArgs, + docstring: String, + ) -> syn::Result<Self> { + Self::new(FnKind::Function, sig, args.name, args.defaults, docstring) } - pub(crate) fn new_method(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> { - Self::new(FnKind::Method { self_ident }, sig) + pub(crate) fn new_method( + self_ident: Ident, + sig: syn::Signature, + args: ExportedImplFnArgs, + docstring: String, + ) -> syn::Result<Self> { + Self::new( + FnKind::Method { self_ident }, + sig, + args.name, + args.defaults, + docstring, + ) } - pub(crate) fn new_constructor(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> { - Self::new(FnKind::Constructor { self_ident }, sig) + pub(crate) fn new_constructor( + self_ident: Ident, + sig: syn::Signature, + args: ExportedImplFnArgs, + docstring: String, + ) -> syn::Result<Self> { + Self::new( + FnKind::Constructor { self_ident }, + sig, + args.name, + args.defaults, + docstring, + ) } pub(crate) fn new_trait_method( self_ident: Ident, sig: syn::Signature, + args: ExportedImplFnArgs, index: u32, + docstring: String, ) -> syn::Result<Self> { - Self::new(FnKind::TraitMethod { self_ident, index }, sig) + Self::new( + FnKind::TraitMethod { self_ident, index }, + sig, + args.name, + args.defaults, + docstring, + ) } - pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result<Self> { + pub(crate) fn new( + kind: FnKind, + sig: syn::Signature, + name: Option<String>, + mut defaults: DefaultMap, + docstring: String, + ) -> syn::Result<Self> { let span = sig.span(); let ident = sig.ident; let looks_like_result = looks_like_result(&sig.output); @@ -56,14 +101,11 @@ impl FnSignature { }; let is_async = sig.asyncness.is_some(); - if is_async && matches!(kind, FnKind::Constructor { .. }) { - return Err(syn::Error::new( - span, - "Async constructors are not supported", - )); - } - - let mut input_iter = sig.inputs.into_iter().map(Arg::try_from).peekable(); + let mut input_iter = sig + .inputs + .into_iter() + .map(|a| Arg::new(a, &mut defaults)) + .peekable(); let receiver = input_iter .next_if(|a| matches!(a, Ok(a) if a.is_receiver())) @@ -84,29 +126,43 @@ impl FnSignature { }) }) .collect::<syn::Result<Vec<_>>>()?; - let mod_path = mod_path()?; + + if let Some(ident) = defaults.idents().first() { + return Err(syn::Error::new( + ident.span(), + format!("Unknown default argument: {}", ident), + )); + } Ok(Self { kind, span, - mod_path, - name: ident_to_string(&ident), + mod_path: mod_path()?, + name: name.unwrap_or_else(|| ident_to_string(&ident)), ident, is_async, receiver, args, return_ty: output, looks_like_result, + docstring, }) } - pub fn return_impl(&self) -> TokenStream { + pub fn lower_return_impl(&self) -> TokenStream { let return_ty = &self.return_ty; quote! { <#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>> } } + pub fn lift_return_impl(&self) -> TokenStream { + let return_ty = &self.return_ty; + quote! { + <#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>> + } + } + /// Generate a closure that tries to lift all arguments into a tuple. /// /// The closure moves all scaffolding arguments into itself and returns: @@ -151,14 +207,6 @@ impl FnSignature { quote! { #(#args),* } } - /// Write expressions for each of our arguments - pub fn write_exprs<'a>( - &'a self, - buf_ident: &'a Ident, - ) -> impl Iterator<Item = TokenStream> + 'a { - self.args.iter().map(|a| a.write_expr(buf_ident)) - } - /// Parameters expressions for each of our arguments pub fn params(&self) -> impl Iterator<Item = TokenStream> + '_ { self.args.iter().map(NamedArg::param) @@ -182,8 +230,18 @@ impl FnSignature { } /// Scaffolding parameters expressions for each of our arguments - pub fn scaffolding_params(&self) -> impl Iterator<Item = TokenStream> + '_ { - self.args.iter().map(NamedArg::scaffolding_param) + pub fn scaffolding_param_names(&self) -> impl Iterator<Item = TokenStream> + '_ { + self.args.iter().map(|a| { + let ident = &a.ident; + quote! { #ident } + }) + } + + pub fn scaffolding_param_types(&self) -> impl Iterator<Item = TokenStream> + '_ { + self.args.iter().map(|a| { + let lift_impl = a.lift_impl(); + quote! { #lift_impl::FfiType } + }) } /// Generate metadata items for this function @@ -193,6 +251,7 @@ impl FnSignature { return_ty, is_async, mod_path, + docstring, .. } = &self; let args_len = try_metadata_value_from_usize( @@ -201,7 +260,11 @@ impl FnSignature { self.args.len(), "UniFFI limits functions to 256 arguments", )?; - let arg_metadata_calls = self.args.iter().map(NamedArg::arg_metadata); + let arg_metadata_calls = self + .args + .iter() + .map(NamedArg::arg_metadata) + .collect::<syn::Result<Vec<_>>>()?; match &self.kind { FnKind::Function => Ok(quote! { @@ -212,6 +275,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + .concat_long_str(#docstring) }), FnKind::Method { self_ident } => { @@ -225,6 +289,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + .concat_long_str(#docstring) }) } @@ -240,6 +305,7 @@ impl FnSignature { .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + .concat_long_str(#docstring) }) } @@ -250,9 +316,11 @@ impl FnSignature { .concat_str(#mod_path) .concat_str(#object_name) .concat_str(#name) + .concat_bool(#is_async) .concat_value(#args_len) #(#arg_metadata_calls)* .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + .concat_long_str(#docstring) }) } } @@ -300,6 +368,69 @@ impl FnSignature { } } + /// Generate metadata items for callback interfaces + /// + /// Unfortunately, most of this is duplicate code from [Self::metadata_items] and + /// [Self::metadata_expr]. However, one issue with that code is that it needs to assume if the + /// arguments are being lifted vs lowered in order to get TYPE_ID_META. That code uses + /// `<Type as Lift>::TYPE_ID_META` for arguments and `<Type as LowerReturn>::TYPE_ID_META` for + /// return types, which works for accidental/historical reasons. + /// + /// The one exception is callback interfaces (#1947), which are handled by this method. + /// + /// TODO: fix the metadata system so that this is not needed. + pub(crate) fn metadata_items_for_callback_interface(&self) -> syn::Result<TokenStream> { + let Self { + name, + return_ty, + is_async, + mod_path, + docstring, + .. + } = &self; + match &self.kind { + FnKind::TraitMethod { + self_ident, index, .. + } => { + let object_name = ident_to_string(self_ident); + let args_len = try_metadata_value_from_usize( + // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self + // params + self.args.len(), + "UniFFI limits functions to 256 arguments", + )?; + let arg_metadata_calls = self + .args + .iter() + .map(NamedArg::arg_metadata) + .collect::<syn::Result<Vec<_>>>()?; + let metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_u32(#index) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>::TYPE_ID_META) + .concat_long_str(#docstring) + }; + Ok(create_metadata_items( + "method", + &format!("{object_name}_{name}"), + metadata_expr, + Some(self.checksum_symbol_name()), + )) + } + + // This should never happen and indicates an error in the internal code + _ => panic!( + "metadata_items_for_callback_interface can only be called with `TraitMethod` sigs" + ), + } + } + pub(crate) fn checksum_symbol_name(&self) -> String { let name = &self.name; match &self.kind { @@ -331,19 +462,11 @@ pub(crate) enum ArgKind { } impl Arg { - pub(crate) fn is_receiver(&self) -> bool { - matches!(self.kind, ArgKind::Receiver(_)) - } -} - -impl TryFrom<FnArg> for Arg { - type Error = syn::Error; - - fn try_from(syn_arg: FnArg) -> syn::Result<Self> { + fn new(syn_arg: FnArg, defaults: &mut DefaultMap) -> syn::Result<Self> { let span = syn_arg.span(); let kind = match syn_arg { FnArg::Typed(p) => match *p.pat { - Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty))), + Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty, defaults)?)), _ => Err(syn::Error::new_spanned(p, "Argument name missing")), }, FnArg::Receiver(receiver) => Ok(ArgKind::Receiver(ReceiverArg::from(receiver))), @@ -351,6 +474,10 @@ impl TryFrom<FnArg> for Arg { Ok(Self { span, kind }) } + + pub(crate) fn is_receiver(&self) -> bool { + matches!(self.kind, ArgKind::Receiver(_)) + } } pub(crate) enum ReceiverArg { @@ -379,27 +506,30 @@ pub(crate) struct NamedArg { pub(crate) name: String, pub(crate) ty: TokenStream, pub(crate) ref_type: Option<Type>, + pub(crate) default: Option<DefaultValue>, } impl NamedArg { - pub(crate) fn new(ident: Ident, ty: &Type) -> Self { - match ty { + pub(crate) fn new(ident: Ident, ty: &Type, defaults: &mut DefaultMap) -> syn::Result<Self> { + Ok(match ty { Type::Reference(r) => { let inner = &r.elem; Self { name: ident_to_string(&ident), - ident, ty: quote! { <#inner as ::uniffi::LiftRef<crate::UniFfiTag>>::LiftType }, ref_type: Some(*inner.clone()), + default: defaults.remove(&ident), + ident, } } _ => Self { name: ident_to_string(&ident), - ident, ty: quote! { #ty }, ref_type: None, + default: defaults.remove(&ident), + ident, }, - } + }) } pub(crate) fn lift_impl(&self) -> TokenStream { @@ -419,27 +549,15 @@ impl NamedArg { quote! { #ident: #ty } } - /// Generate the scaffolding parameter for this Arg - pub(crate) fn scaffolding_param(&self) -> TokenStream { - let ident = &self.ident; - let lift_impl = self.lift_impl(); - quote! { #ident: #lift_impl::FfiType } - } - - /// Generate the expression to write the scaffolding parameter for this arg - pub(crate) fn write_expr(&self, buf_ident: &Ident) -> TokenStream { - let ident = &self.ident; - let lower_impl = self.lower_impl(); - quote! { #lower_impl::write(#ident, &mut #buf_ident) } - } - - pub(crate) fn arg_metadata(&self) -> TokenStream { + pub(crate) fn arg_metadata(&self) -> syn::Result<TokenStream> { let name = &self.name; let lift_impl = self.lift_impl(); - quote! { + let default_calls = default_value_metadata_calls(&self.default)?; + Ok(quote! { .concat_str(#name) .concat(#lift_impl::TYPE_ID_META) - } + #default_calls + }) } } diff --git a/third_party/rust/uniffi_macros/src/lib.rs b/third_party/rust/uniffi_macros/src/lib.rs index 4cffddfa0e..929400c885 100644 --- a/third_party/rust/uniffi_macros/src/lib.rs +++ b/third_party/rust/uniffi_macros/src/lib.rs @@ -5,10 +5,8 @@ #![warn(rust_2018_idioms, unused_qualifications)] //! Macros for `uniffi`. -//! -//! Currently this is just for easily generating integration tests, but maybe -//! we'll put some other code-annotation helper macros in here at some point. +#[cfg(feature = "trybuild")] use camino::Utf8Path; use proc_macro::TokenStream; use quote::quote; @@ -18,6 +16,7 @@ use syn::{ }; mod custom; +mod default; mod enum_; mod error; mod export; @@ -33,20 +32,6 @@ use self::{ record::expand_record, }; -struct IdentPair { - lhs: Ident, - rhs: Ident, -} - -impl Parse for IdentPair { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - let lhs = input.parse()?; - input.parse::<Token![,]>()?; - let rhs = input.parse()?; - Ok(Self { lhs, rhs }) - } -} - struct CustomTypeInfo { ident: Ident, builtin: Path, @@ -107,9 +92,8 @@ fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> Toke let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone())); let gen_output = || { - let args = syn::parse(attr_args)?; let item = syn::parse(input)?; - expand_export(item, args, udl_mode) + expand_export(item, attr_args, udl_mode) }; let output = gen_output().unwrap_or_else(syn::Error::into_compile_error); @@ -129,7 +113,7 @@ pub fn derive_record(input: TokenStream) -> TokenStream { #[proc_macro_derive(Enum)] pub fn derive_enum(input: TokenStream) -> TokenStream { - expand_enum(parse_macro_input!(input), false) + expand_enum(parse_macro_input!(input), None, false) .unwrap_or_else(syn::Error::into_compile_error) .into() } @@ -225,10 +209,14 @@ pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenSt #[doc(hidden)] #[proc_macro_attribute] -pub fn derive_enum_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { - expand_enum(syn::parse_macro_input!(input), true) - .unwrap_or_else(syn::Error::into_compile_error) - .into() +pub fn derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { + expand_enum( + syn::parse_macro_input!(input), + Some(syn::parse_macro_input!(attrs)), + true, + ) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[doc(hidden)] @@ -257,22 +245,6 @@ pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { do_export(attrs, input, true) } -/// Generate various support elements, including the FfiConverter implementation, -/// for a trait interface for the scaffolding code -#[doc(hidden)] -#[proc_macro] -pub fn expand_trait_interface_support(tokens: TokenStream) -> TokenStream { - export::ffi_converter_trait_impl(&syn::parse_macro_input!(tokens), true).into() -} - -/// Generate the FfiConverter implementation for an trait interface for the scaffolding code -#[doc(hidden)] -#[proc_macro] -pub fn scaffolding_ffi_converter_callback_interface(tokens: TokenStream) -> TokenStream { - let input: IdentPair = syn::parse_macro_input!(tokens); - export::ffi_converter_callback_interface_impl(&input.lhs, &input.rhs, true).into() -} - /// A helper macro to include generated component scaffolding. /// /// This is a simple convenience macro to include the UniFFI component @@ -373,6 +345,7 @@ pub fn use_udl_object(tokens: TokenStream) -> TokenStream { /// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl"); /// ``` #[proc_macro] +#[cfg(feature = "trybuild")] pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream { let udl_file = syn::parse_macro_input!(udl_file as LitStr); let udl_file_string = udl_file.value(); @@ -396,15 +369,25 @@ pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream { }.into() } -/// A dummy macro that does nothing. +/// An attribute for constructors. +/// +/// Constructors are in `impl` blocks which have a `#[uniffi::export]` attribute, /// /// This exists so `#[uniffi::export]` can emit its input verbatim without -/// causing unexpected errors, plus some extra code in case everything is okay. +/// causing unexpected errors in the entire exported block. +/// This happens very often when the proc-macro is run on an incomplete +/// input by rust-analyzer while the developer is typing. /// -/// It is important for `#[uniffi::export]` to not raise unexpected errors if it -/// fails to parse the input as this happens very often when the proc-macro is -/// run on an incomplete input by rust-analyzer while the developer is typing. +/// So much better to do nothing here then let the impl block find the attribute. #[proc_macro_attribute] pub fn constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream { input } + +/// An attribute for methods. +/// +/// Everything above applies here too. +#[proc_macro_attribute] +pub fn method(_attrs: TokenStream, input: TokenStream) -> TokenStream { + input +} diff --git a/third_party/rust/uniffi_macros/src/object.rs b/third_party/rust/uniffi_macros/src/object.rs index 573a1eaadd..6bcc07a14e 100644 --- a/third_party/rust/uniffi_macros/src/object.rs +++ b/third_party/rust/uniffi_macros/src/object.rs @@ -1,17 +1,27 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::DeriveInput; -use uniffi_meta::free_fn_symbol_name; -use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}; +use crate::util::{ + create_metadata_items, extract_docstring, ident_to_string, mod_path, tagged_impl_header, +}; +use uniffi_meta::ObjectImpl; pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> { let module_path = mod_path()?; let ident = &input.ident; + let docstring = extract_docstring(&input.attrs)?; let name = ident_to_string(ident); - let free_fn_ident = Ident::new(&free_fn_symbol_name(&module_path, &name), Span::call_site()); + let clone_fn_ident = Ident::new( + &uniffi_meta::clone_fn_symbol_name(&module_path, &name), + Span::call_site(), + ); + let free_fn_ident = Ident::new( + &uniffi_meta::free_fn_symbol_name(&module_path, &name), + Span::call_site(), + ); let meta_static_var = (!udl_mode).then(|| { - interface_meta_static_var(ident, false, &module_path) + interface_meta_static_var(ident, ObjectImpl::Struct, &module_path, docstring) .unwrap_or_else(syn::Error::into_compile_error) }); let interface_impl = interface_impl(ident, udl_mode); @@ -19,7 +29,19 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr Ok(quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #free_fn_ident( + pub unsafe extern "C" fn #clone_fn_ident( + ptr: *const ::std::ffi::c_void, + call_status: &mut ::uniffi::RustCallStatus + ) -> *const ::std::ffi::c_void { + uniffi::rust_call(call_status, || { + unsafe { ::std::sync::Arc::increment_strong_count(ptr) }; + Ok(ptr) + }) + } + + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #free_fn_ident( ptr: *const ::std::ffi::c_void, call_status: &mut ::uniffi::RustCallStatus ) { @@ -41,6 +63,7 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { let name = ident_to_string(ident); let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode); + let lower_return_impl_spec = tagged_impl_header("LowerReturn", ident, udl_mode); let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let mod_path = match mod_path() { Ok(p) => p, @@ -52,7 +75,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { // if they are not, but unfortunately it fails with an unactionably obscure error message. // By asserting the requirement explicitly, we help Rust produce a more scrutable error message // and thus help the user debug why the requirement isn't being met. - uniffi::deps::static_assertions::assert_impl_all!(#ident: Sync, Send); + uniffi::deps::static_assertions::assert_impl_all!(#ident: ::core::marker::Sync, ::core::marker::Send); #[doc(hidden)] #[automatically_derived] @@ -78,17 +101,10 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { ::std::sync::Arc::into_raw(obj) as Self::FfiType } - /// When lifting, we receive a "borrow" of the `Arc` that is owned by - /// the foreign-language code, and make a clone of it for our own use. - /// - /// Safety: the provided value must be a pointer previously obtained by calling - /// the `lower()` or `write()` method of this impl. + /// When lifting, we receive an owned `Arc` that the foreign language code cloned. fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> { let v = v as *const #ident; - // We musn't drop the `Arc` that is owned by the foreign-language code. - let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { ::std::sync::Arc::<Self>::from_raw(v) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(&*foreign_arc)) + Ok(unsafe { ::std::sync::Arc::<Self>::from_raw(v) }) } /// When writing as a field of a complex structure, make a clone and transfer ownership @@ -117,8 +133,17 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) .concat_str(#mod_path) - .concat_str(#name) - .concat_bool(false); + .concat_str(#name); + } + + unsafe #lower_return_impl_spec { + type ReturnType = <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::FfiType; + + fn lower_return(obj: Self) -> ::std::result::Result<Self::ReturnType, ::uniffi::RustBuffer> { + Ok(<Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(::std::sync::Arc::new(obj))) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::TYPE_ID_META; } unsafe #lift_ref_impl_spec { @@ -129,18 +154,25 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { pub(crate) fn interface_meta_static_var( ident: &Ident, - is_trait: bool, + imp: ObjectImpl, module_path: &str, + docstring: String, ) -> syn::Result<TokenStream> { let name = ident_to_string(ident); + let code = match imp { + ObjectImpl::Struct => quote! { ::uniffi::metadata::codes::INTERFACE }, + ObjectImpl::Trait => quote! { ::uniffi::metadata::codes::TRAIT_INTERFACE }, + ObjectImpl::CallbackTrait => quote! { ::uniffi::metadata::codes::CALLBACK_TRAIT_INTERFACE }, + }; + Ok(create_metadata_items( "interface", &name, quote! { - ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::INTERFACE) - .concat_str(#module_path) - .concat_str(#name) - .concat_bool(#is_trait) + ::uniffi::MetadataBuffer::from_code(#code) + .concat_str(#module_path) + .concat_str(#name) + .concat_long_str(#docstring) }, None, )) diff --git a/third_party/rust/uniffi_macros/src/record.rs b/third_party/rust/uniffi_macros/src/record.rs index abf2743ec6..41f5d016ac 100644 --- a/third_party/rust/uniffi_macros/src/record.rs +++ b/third_party/rust/uniffi_macros/src/record.rs @@ -1,17 +1,20 @@ use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{ - parse::{Parse, ParseStream}, - Data, DataStruct, DeriveInput, Field, Lit, Token, -}; - -use crate::util::{ - create_metadata_items, derive_all_ffi_traits, either_attribute_arg, ident_to_string, kw, - mod_path, tagged_impl_header, try_metadata_value_from_usize, try_read_field, AttributeSliceExt, - UniffiAttributeArgs, +use quote::quote; +use syn::{parse::ParseStream, Data, DataStruct, DeriveInput, Field, Token}; + +use crate::{ + default::{default_value_metadata_calls, DefaultValue}, + util::{ + create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring, + ident_to_string, kw, mod_path, tagged_impl_header, try_metadata_value_from_usize, + try_read_field, AttributeSliceExt, UniffiAttributeArgs, + }, }; pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> { + if let Some(e) = input.attrs.uniffi_attr_args_not_allowed_here() { + return Err(e); + } let record = match input.data { Data::Struct(s) => s, _ => { @@ -23,10 +26,12 @@ pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr }; let ident = &input.ident; + let docstring = extract_docstring(&input.attrs)?; let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode) .unwrap_or_else(syn::Error::into_compile_error); let meta_static_var = (!udl_mode).then(|| { - record_meta_static_var(ident, &record).unwrap_or_else(syn::Error::into_compile_error) + record_meta_static_var(ident, docstring, &record) + .unwrap_or_else(syn::Error::into_compile_error) }); Ok(quote! { @@ -78,35 +83,9 @@ fn write_field(f: &Field) -> TokenStream { } } -pub enum FieldDefault { - Literal(Lit), - Null(kw::None), -} - -impl ToTokens for FieldDefault { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - FieldDefault::Literal(lit) => lit.to_tokens(tokens), - FieldDefault::Null(kw) => kw.to_tokens(tokens), - } - } -} - -impl Parse for FieldDefault { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::None) { - let none_kw: kw::None = input.parse()?; - Ok(Self::Null(none_kw)) - } else { - Ok(Self::Literal(input.parse()?)) - } - } -} - #[derive(Default)] pub struct FieldAttributeArguments { - pub(crate) default: Option<FieldDefault>, + pub(crate) default: Option<DefaultValue>, } impl UniffiAttributeArgs for FieldAttributeArguments { @@ -128,6 +107,7 @@ impl UniffiAttributeArgs for FieldAttributeArguments { pub(crate) fn record_meta_static_var( ident: &Ident, + docstring: String, record: &DataStruct, ) -> syn::Result<TokenStream> { let name = ident_to_string(ident); @@ -144,17 +124,9 @@ pub(crate) fn record_meta_static_var( .parse_uniffi_attr_args::<FieldAttributeArguments>()?; let name = ident_to_string(f.ident.as_ref().unwrap()); + let docstring = extract_docstring(&f.attrs)?; let ty = &f.ty; - let default = match attrs.default { - Some(default) => { - let default_value = default_value_concat_calls(default)?; - quote! { - .concat_bool(true) - #default_value - } - } - None => quote! { .concat_bool(false) }, - }; + let default = default_value_metadata_calls(&attrs.default)?; // Note: fields need to implement both `Lower` and `Lift` to be used in a record. The // TYPE_ID_META should be the same for both traits. @@ -162,6 +134,7 @@ pub(crate) fn record_meta_static_var( .concat_str(#name) .concat(<#ty as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) #default + .concat_long_str(#docstring) }) }) .collect::<syn::Result<_>>()?; @@ -175,50 +148,8 @@ pub(crate) fn record_meta_static_var( .concat_str(#name) .concat_value(#fields_len) #concat_fields + .concat_long_str(#docstring) }, None, )) } - -fn default_value_concat_calls(default: FieldDefault) -> syn::Result<TokenStream> { - match default { - FieldDefault::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err( - syn::Error::new_spanned(i, "integer literals with suffix not supported here"), - ), - FieldDefault::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err( - syn::Error::new_spanned(f, "float literals with suffix not supported here"), - ), - - FieldDefault::Literal(Lit::Str(s)) => Ok(quote! { - .concat_value(::uniffi::metadata::codes::LIT_STR) - .concat_str(#s) - }), - FieldDefault::Literal(Lit::Int(i)) => { - let digits = i.base10_digits(); - Ok(quote! { - .concat_value(::uniffi::metadata::codes::LIT_INT) - .concat_str(#digits) - }) - } - FieldDefault::Literal(Lit::Float(f)) => { - let digits = f.base10_digits(); - Ok(quote! { - .concat_value(::uniffi::metadata::codes::LIT_FLOAT) - .concat_str(#digits) - }) - } - FieldDefault::Literal(Lit::Bool(b)) => Ok(quote! { - .concat_value(::uniffi::metadata::codes::LIT_BOOL) - .concat_bool(#b) - }), - - FieldDefault::Literal(_) => Err(syn::Error::new_spanned( - default, - "this type of literal is not currently supported as a default", - )), - - FieldDefault::Null(_) => Ok(quote! { - .concat_value(::uniffi::metadata::codes::LIT_NULL) - }), - } -} diff --git a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs index afdb119cc4..c82e9389bb 100644 --- a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs +++ b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs @@ -20,15 +20,7 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> { let ffi_rustbuffer_free_ident = format_ident!("ffi_{module_path}_rustbuffer_free"); let ffi_rustbuffer_reserve_ident = format_ident!("ffi_{module_path}_rustbuffer_reserve"); let reexport_hack_ident = format_ident!("{module_path}_uniffi_reexport_hack"); - let ffi_foreign_executor_callback_set_ident = - format_ident!("ffi_{module_path}_foreign_executor_callback_set"); - let ffi_rust_future_continuation_callback_set = - format_ident!("ffi_{module_path}_rust_future_continuation_callback_set"); let ffi_rust_future_scaffolding_fns = rust_future_scaffolding_fns(&module_path); - let continuation_cell = format_ident!( - "RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}", - module_path.to_uppercase() - ); Ok(quote! { // Unit struct to parameterize the FfiConverter trait. @@ -68,7 +60,7 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status) } @@ -89,31 +81,10 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status) } - static #continuation_cell: ::uniffi::deps::once_cell::sync::OnceCell<::uniffi::RustFutureContinuationCallback> = ::uniffi::deps::once_cell::sync::OnceCell::new(); - - #[allow(clippy::missing_safety_doc, missing_docs)] - #[doc(hidden)] - #[no_mangle] - pub extern "C" fn #ffi_foreign_executor_callback_set_ident(callback: uniffi::ffi::ForeignExecutorCallback) { - uniffi::ffi::foreign_executor_callback_set(callback) - } - - #[allow(clippy::missing_safety_doc, missing_docs)] - #[doc(hidden)] - #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_continuation_callback_set(callback: ::uniffi::RustFutureContinuationCallback) { - if let Err(existing) = #continuation_cell.set(callback) { - // Don't panic if this to be called multiple times with the same callback. - if existing != callback { - panic!("Attempt to set the RustFuture continuation callback twice"); - } - } - } - #ffi_rust_future_scaffolding_fns // Code to re-export the UniFFI scaffolding functions. @@ -158,12 +129,12 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> { /// Generates the rust_future_* functions /// -/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents +/// The foreign side uses a type-erased `Handle` to interact with futures, which presents /// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`? /// /// Handle this by using some brute-force monomorphization. For each possible ffi type, we /// generate a set of scaffolding functions. The bindings code is responsible for calling the one -/// corresponds the scaffolding function that created the `RustFutureHandle`. +/// corresponds the scaffolding function that created the `Handle`. /// /// This introduces safety issues, but we do get some type checking. If the bindings code calls /// the wrong rust_future_complete function, they should get an unexpected return type, which @@ -190,41 +161,37 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { let ffi_rust_future_cancel = format_ident!("ffi_{module_path}_rust_future_cancel_{fn_suffix}"); let ffi_rust_future_complete = format_ident!("ffi_{module_path}_rust_future_complete_{fn_suffix}"); let ffi_rust_future_free = format_ident!("ffi_{module_path}_rust_future_free_{fn_suffix}"); - let continuation_cell = format_ident!("RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}", module_path.to_uppercase()); quote! { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, data: *const ()) { - let callback = #continuation_cell - .get() - .expect("RustFuture continuation callback not set. This is likely a uniffi bug."); - ::uniffi::ffi::rust_future_poll::<#return_type>(handle, *callback, data); + pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: u64) { + ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data); } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_cancel::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn #ffi_rust_future_complete( - handle: ::uniffi::RustFutureHandle, + handle: ::uniffi::Handle, out_status: &mut ::uniffi::RustCallStatus ) -> #return_type { - ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status) + ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_free::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle) } } }) diff --git a/third_party/rust/uniffi_macros/src/util.rs b/third_party/rust/uniffi_macros/src/util.rs index 9f213ea1d7..97faad9c4d 100644 --- a/third_party/rust/uniffi_macros/src/util.rs +++ b/third_party/rust/uniffi_macros/src/util.rs @@ -8,7 +8,7 @@ use std::path::{Path as StdPath, PathBuf}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, - Attribute, Token, + Attribute, Expr, Lit, Token, }; pub fn manifest_path() -> Result<PathBuf, String> { @@ -79,8 +79,13 @@ pub fn try_read_field(f: &syn::Field) -> TokenStream { let ident = &f.ident; let ty = &f.ty; - quote! { - #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?, + match ident { + Some(ident) => quote! { + #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?, + }, + None => quote! { + <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?, + }, } } @@ -151,13 +156,7 @@ pub fn parse_comma_separated<T: UniffiAttributeArgs>(input: ParseStream<'_>) -> } #[derive(Default)] -pub struct ArgumentNotAllowedHere; - -impl Parse for ArgumentNotAllowedHere { - fn parse(input: ParseStream<'_>) -> syn::Result<Self> { - parse_comma_separated(input) - } -} +struct ArgumentNotAllowedHere; impl UniffiAttributeArgs for ArgumentNotAllowedHere { fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { @@ -224,7 +223,11 @@ pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { } } -pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]) -> TokenStream { +pub(crate) fn derive_ffi_traits( + ty: impl ToTokens, + udl_mode: bool, + trait_names: &[&str], +) -> TokenStream { let trait_idents = trait_names .iter() .map(|name| Ident::new(name, Span::call_site())); @@ -247,11 +250,14 @@ pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str] pub mod kw { syn::custom_keyword!(async_runtime); syn::custom_keyword!(callback_interface); - syn::custom_keyword!(constructor); + syn::custom_keyword!(with_foreign); syn::custom_keyword!(default); syn::custom_keyword!(flat_error); syn::custom_keyword!(None); + syn::custom_keyword!(Some); syn::custom_keyword!(with_try_read); + syn::custom_keyword!(name); + syn::custom_keyword!(non_exhaustive); syn::custom_keyword!(Debug); syn::custom_keyword!(Display); syn::custom_keyword!(Eq); @@ -276,3 +282,20 @@ impl Parse for ExternalTypeItem { }) } } + +pub(crate) fn extract_docstring(attrs: &[Attribute]) -> syn::Result<String> { + return attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + .map(|attr| { + let name_value = attr.meta.require_name_value()?; + if let Expr::Lit(expr) = &name_value.value { + if let Lit::Str(lit_str) = &expr.lit { + return Ok(lit_str.value().trim().to_owned()); + } + } + Err(syn::Error::new_spanned(attr, "Cannot parse doc attribute")) + }) + .collect::<syn::Result<Vec<_>>>() + .map(|lines| lines.join("\n")); +} diff --git a/third_party/rust/uniffi_meta/.cargo-checksum.json b/third_party/rust/uniffi_meta/.cargo-checksum.json index cb02cde83f..31b45ce807 100644 --- a/third_party/rust/uniffi_meta/.cargo-checksum.json +++ b/third_party/rust/uniffi_meta/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"cb9f8aad563572bd4f12ee234ede6773f189a79ba5bd3bfd7622d3c0ec49d6a3","src/ffi_names.rs":"422bbe9d49d5476de752a9f9b2330f59b37a79e67f19a828caceb64d1bdabff8","src/group.rs":"ae996e6b9f83d459af04eb392e36487d0fe19c7328a395823186cce76a0955ff","src/lib.rs":"a442e2271a0eb538ec1d4fc7573a3acc7e5f366e2b2ac8d0e659fd998fd7d995","src/metadata.rs":"4ae425a8eab7b8c19a6b96c914f2c02c5bee00358888fd55b936fd1fd175a93c","src/reader.rs":"57fb771584491b8e90b01c68f9d53bac7cfa3135888e11e24e14b59312185ff9","src/types.rs":"8c155ed1301e11a365863989e29c2271149048092fb7052ec145f58c948482d5"},"package":"71dc8573a7b1ac4b71643d6da34888273ebfc03440c525121f1b3634ad3417a2"}
\ No newline at end of file +{"files":{"Cargo.toml":"5620cf9840477b158641547703ba353e3ad8427ec7b20b9dd5e5f5fe4df7d6d2","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/ffi_names.rs":"ca38b700a0a103c9faaf456ed91b67adf46d4e750aee9e9cd01ad97fb1840494","src/group.rs":"d0a43f3c528aba9403649715981ad3a8849d7a370f4ef9e2d618b88f60a3102f","src/lib.rs":"3f00d5214e2785e4b3045bc48899f6f6b1dce32ab3da6be3ebce716ee9d24c5f","src/metadata.rs":"3f236b337a1fd5082ea9cc4fee6800193a903ee88b81f1c3202843402f122a14","src/reader.rs":"579e2b87d8dd9d703b8811294abfb992621c0a46765800e4db2fad2906db2208","src/types.rs":"c2c5188da8cdf5af7f8496d4660bcfaa971b81ed73b64486c05b47256048544f"},"package":"f7224422c4cfd181c7ca9fca2154abca4d21db962f926f270f996edd38b0c4b8"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_meta/Cargo.toml b/third_party/rust/uniffi_meta/Cargo.toml index 34999eee18..04d8170011 100644 --- a/third_party/rust/uniffi_meta/Cargo.toml +++ b/third_party/rust/uniffi_meta/Cargo.toml @@ -12,9 +12,10 @@ [package] edition = "2021" name = "uniffi_meta" -version = "0.25.3" +version = "0.27.1" description = "uniffi_meta" homepage = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -32,4 +33,4 @@ version = "1.3" version = "0.3" [dependencies.uniffi_checksum_derive] -version = "0.25.3" +version = "0.27.1" diff --git a/third_party/rust/uniffi_meta/README.md b/third_party/rust/uniffi_meta/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_meta/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_meta/src/ffi_names.rs b/third_party/rust/uniffi_meta/src/ffi_names.rs index 44a5bc3e63..5c931a09e3 100644 --- a/third_party/rust/uniffi_meta/src/ffi_names.rs +++ b/third_party/rust/uniffi_meta/src/ffi_names.rs @@ -33,6 +33,12 @@ pub fn method_symbol_name(namespace: &str, object_name: &str, name: &str) -> Str format!("uniffi_{namespace}_fn_method_{object_name}_{name}") } +/// FFI symbol name for the `clone` function for an object. +pub fn clone_fn_symbol_name(namespace: &str, object_name: &str) -> String { + let object_name = object_name.to_ascii_lowercase(); + format!("uniffi_{namespace}_fn_clone_{object_name}") +} + /// FFI symbol name for the `free` function for an object. pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String { let object_name = object_name.to_ascii_lowercase(); @@ -40,9 +46,12 @@ pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String { } /// FFI symbol name for the `init_callback` function for a callback interface -pub fn init_callback_fn_symbol_name(namespace: &str, callback_interface_name: &str) -> String { +pub fn init_callback_vtable_fn_symbol_name( + namespace: &str, + callback_interface_name: &str, +) -> String { let callback_interface_name = callback_interface_name.to_ascii_lowercase(); - format!("uniffi_{namespace}_fn_init_callback_{callback_interface_name}") + format!("uniffi_{namespace}_fn_init_callback_vtable_{callback_interface_name}") } /// FFI checksum symbol name for a top-level function diff --git a/third_party/rust/uniffi_meta/src/group.rs b/third_party/rust/uniffi_meta/src/group.rs index f0be2e5a98..a41776bf8a 100644 --- a/third_party/rust/uniffi_meta/src/group.rs +++ b/third_party/rust/uniffi_meta/src/group.rs @@ -18,6 +18,7 @@ pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap { Metadata::Namespace(namespace) => { let group = MetadataGroup { namespace: namespace.clone(), + namespace_docstring: None, items: BTreeSet::new(), }; Some((namespace.crate_name.clone(), group)) @@ -29,6 +30,7 @@ pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap { }; let group = MetadataGroup { namespace, + namespace_docstring: None, items: BTreeSet::new(), }; Some((udl.module_path.clone(), group)) @@ -63,6 +65,7 @@ pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec<Metadata>) -> #[derive(Debug)] pub struct MetadataGroup { pub namespace: NamespaceMetadata, + pub namespace_docstring: Option<String>, pub items: BTreeSet<Metadata>, } @@ -127,12 +130,6 @@ impl<'a> ExternalTypeConverter<'a> { ..meta }), Metadata::Enum(meta) => Metadata::Enum(self.convert_enum(meta)), - Metadata::Error(meta) => Metadata::Error(match meta { - ErrorMetadata::Enum { enum_, is_flat } => ErrorMetadata::Enum { - enum_: self.convert_enum(enum_), - is_flat, - }, - }), _ => item, } } diff --git a/third_party/rust/uniffi_meta/src/lib.rs b/third_party/rust/uniffi_meta/src/lib.rs index e486d84d89..90f7b2d3cd 100644 --- a/third_party/rust/uniffi_meta/src/lib.rs +++ b/third_party/rust/uniffi_meta/src/lib.rs @@ -23,7 +23,7 @@ mod metadata; // `docs/uniffi-versioning.md` for details. // // Once we get to 1.0, then we'll need to update the scheme to something like 100 + major_version -pub const UNIFFI_CONTRACT_VERSION: u32 = 24; +pub const UNIFFI_CONTRACT_VERSION: u32 = 26; /// Similar to std::hash::Hash. /// @@ -143,6 +143,7 @@ pub struct FnMetadata { pub return_type: Option<Type>, pub throws: Option<Type>, pub checksum: Option<u16>, + pub docstring: Option<String>, } impl FnMetadata { @@ -160,9 +161,11 @@ pub struct ConstructorMetadata { pub module_path: String, pub self_name: String, pub name: String, + pub is_async: bool, pub inputs: Vec<FnParamMetadata>, pub throws: Option<Type>, pub checksum: Option<u16>, + pub docstring: Option<String>, } impl ConstructorMetadata { @@ -190,6 +193,7 @@ pub struct MethodMetadata { pub throws: Option<Type>, pub takes_self_by_arc: bool, // unused except by rust udl bindgen. pub checksum: Option<u16>, + pub docstring: Option<String>, } impl MethodMetadata { @@ -216,6 +220,7 @@ pub struct TraitMethodMetadata { pub throws: Option<Type>, pub takes_self_by_arc: bool, // unused except by rust udl bindgen. pub checksum: Option<u16>, + pub docstring: Option<String>, } impl TraitMethodMetadata { @@ -266,7 +271,17 @@ pub enum LiteralMetadata { Enum(String, Type), EmptySequence, EmptyMap, - Null, + None, + Some { inner: Box<LiteralMetadata> }, +} + +impl LiteralMetadata { + pub fn new_uint(v: u64) -> Self { + LiteralMetadata::UInt(v, Radix::Decimal, Type::UInt64) + } + pub fn new_int(v: i64) -> Self { + LiteralMetadata::Int(v, Radix::Decimal, Type::Int64) + } } // Represent the radix of integer literal values. @@ -283,6 +298,7 @@ pub struct RecordMetadata { pub module_path: String, pub name: String, pub fields: Vec<FieldMetadata>, + pub docstring: Option<String>, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -290,19 +306,26 @@ pub struct FieldMetadata { pub name: String, pub ty: Type, pub default: Option<LiteralMetadata>, + pub docstring: Option<String>, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct EnumMetadata { pub module_path: String, pub name: String, + pub forced_flatness: Option<bool>, pub variants: Vec<VariantMetadata>, + pub discr_type: Option<Type>, + pub non_exhaustive: bool, + pub docstring: Option<String>, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct VariantMetadata { pub name: String, + pub discr: Option<LiteralMetadata>, pub fields: Vec<FieldMetadata>, + pub docstring: Option<String>, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -310,15 +333,25 @@ pub struct ObjectMetadata { pub module_path: String, pub name: String, pub imp: types::ObjectImpl, + pub docstring: Option<String>, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct CallbackInterfaceMetadata { pub module_path: String, pub name: String, + pub docstring: Option<String>, } impl ObjectMetadata { + /// FFI symbol name for the `clone` function for this object. + /// + /// This function is used to increment the reference count before lowering an object to pass + /// back to Rust. + pub fn clone_ffi_symbol_name(&self) -> String { + clone_fn_symbol_name(&self.module_path, &self.name) + } + /// FFI symbol name for the `free` function for this object. /// /// This function is used to free the memory used by this object. @@ -368,6 +401,7 @@ impl UniffiTraitMetadata { } #[repr(u8)] +#[derive(Eq, PartialEq, Hash)] pub enum UniffiTraitDiscriminants { Debug, Display, @@ -388,25 +422,6 @@ impl UniffiTraitDiscriminants { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum ErrorMetadata { - Enum { enum_: EnumMetadata, is_flat: bool }, -} - -impl ErrorMetadata { - pub fn name(&self) -> &String { - match self { - Self::Enum { enum_, .. } => &enum_.name, - } - } - - pub fn module_path(&self) -> &String { - match self { - Self::Enum { enum_, .. } => &enum_.module_path, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct CustomTypeMetadata { pub module_path: String, pub name: String, @@ -433,7 +448,6 @@ pub enum Metadata { CallbackInterface(CallbackInterfaceMetadata), Record(RecordMetadata), Enum(EnumMetadata), - Error(ErrorMetadata), Constructor(ConstructorMetadata), Method(MethodMetadata), TraitMethod(TraitMethodMetadata), @@ -458,7 +472,6 @@ impl Metadata { Metadata::Object(meta) => &meta.module_path, Metadata::CallbackInterface(meta) => &meta.module_path, Metadata::TraitMethod(meta) => &meta.module_path, - Metadata::Error(meta) => meta.module_path(), Metadata::CustomType(meta) => &meta.module_path, Metadata::UniffiTrait(meta) => meta.module_path(), } @@ -507,12 +520,6 @@ impl From<EnumMetadata> for Metadata { } } -impl From<ErrorMetadata> for Metadata { - fn from(e: ErrorMetadata) -> Self { - Self::Error(e) - } -} - impl From<ObjectMetadata> for Metadata { fn from(v: ObjectMetadata) -> Self { Self::Object(v) diff --git a/third_party/rust/uniffi_meta/src/metadata.rs b/third_party/rust/uniffi_meta/src/metadata.rs index 6e490a4866..9cfb77a244 100644 --- a/third_party/rust/uniffi_meta/src/metadata.rs +++ b/third_party/rust/uniffi_meta/src/metadata.rs @@ -7,7 +7,7 @@ // `uniffi_core`. // This is the easy way out of that issue and is a temporary hacky solution. -/// Metadata constants, make sure to keep this in sync with copy in `uniffi_meta::reader` +/// Metadata constants, make sure to keep this in sync with copy in `uniffi_core::metadata` pub mod codes { // Top-level metadata item codes pub const FUNC: u8 = 0; @@ -15,13 +15,14 @@ pub mod codes { pub const RECORD: u8 = 2; pub const ENUM: u8 = 3; pub const INTERFACE: u8 = 4; - pub const ERROR: u8 = 5; pub const NAMESPACE: u8 = 6; pub const CONSTRUCTOR: u8 = 7; pub const UDL_FILE: u8 = 8; pub const CALLBACK_INTERFACE: u8 = 9; pub const TRAIT_METHOD: u8 = 10; pub const UNIFFI_TRAIT: u8 = 11; + pub const TRAIT_INTERFACE: u8 = 12; + pub const CALLBACK_TRAIT_INTERFACE: u8 = 13; //pub const UNKNOWN: u8 = 255; // Type codes @@ -49,8 +50,8 @@ pub mod codes { pub const TYPE_CALLBACK_INTERFACE: u8 = 21; pub const TYPE_CUSTOM: u8 = 22; pub const TYPE_RESULT: u8 = 23; - //pub const TYPE_FUTURE: u8 = 24; - pub const TYPE_FOREIGN_EXECUTOR: u8 = 25; + pub const TYPE_TRAIT_INTERFACE: u8 = 24; + pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25; pub const TYPE_UNIT: u8 = 255; // Literal codes @@ -58,7 +59,9 @@ pub mod codes { pub const LIT_INT: u8 = 1; pub const LIT_FLOAT: u8 = 2; pub const LIT_BOOL: u8 = 3; - pub const LIT_NULL: u8 = 4; + pub const LIT_NONE: u8 = 4; + pub const LIT_SOME: u8 = 5; + pub const LIT_EMPTY_SEQ: u8 = 6; } // Create a checksum for a MetadataBuffer diff --git a/third_party/rust/uniffi_meta/src/reader.rs b/third_party/rust/uniffi_meta/src/reader.rs index bf6525f2b5..6fec6cb3a3 100644 --- a/third_party/rust/uniffi_meta/src/reader.rs +++ b/third_party/rust/uniffi_meta/src/reader.rs @@ -52,9 +52,10 @@ impl<'a> MetadataReader<'a> { codes::CONSTRUCTOR => self.read_constructor()?.into(), codes::METHOD => self.read_method()?.into(), codes::RECORD => self.read_record()?.into(), - codes::ENUM => self.read_enum(false)?.into(), - codes::ERROR => self.read_error()?.into(), - codes::INTERFACE => self.read_object()?.into(), + codes::ENUM => self.read_enum()?.into(), + codes::INTERFACE => self.read_object(ObjectImpl::Struct)?.into(), + codes::TRAIT_INTERFACE => self.read_object(ObjectImpl::Trait)?.into(), + codes::CALLBACK_TRAIT_INTERFACE => self.read_object(ObjectImpl::CallbackTrait)?.into(), codes::CALLBACK_INTERFACE => self.read_callback_interface()?.into(), codes::TRAIT_METHOD => self.read_trait_method()?.into(), codes::UNIFFI_TRAIT => self.read_uniffi_trait()?.into(), @@ -80,6 +81,17 @@ impl<'a> MetadataReader<'a> { } } + fn read_u16(&mut self) -> Result<u16> { + if self.buf.len() >= 2 { + // read the value as little-endian + let value = u16::from_le_bytes([self.buf[0], self.buf[1]]); + self.buf = &self.buf[2..]; + Ok(value) + } else { + bail!("Not enough data left in buffer to read a u16 value"); + } + } + fn read_u32(&mut self) -> Result<u32> { if self.buf.len() >= 4 { // read the value as little-endian @@ -105,6 +117,17 @@ impl<'a> MetadataReader<'a> { String::from_utf8(slice.into()).context("Invalid string data") } + fn read_long_string(&mut self) -> Result<String> { + let size = self.read_u16()? as usize; + let slice; + (slice, self.buf) = self.buf.split_at(size); + String::from_utf8(slice.into()).context("Invalid string data") + } + + fn read_optional_long_string(&mut self) -> Result<Option<String>> { + Ok(Some(self.read_long_string()?).filter(|str| !str.is_empty())) + } + fn read_type(&mut self) -> Result<Type> { let value = self.read_u8()?; Ok(match value { @@ -122,7 +145,6 @@ impl<'a> MetadataReader<'a> { codes::TYPE_STRING => Type::String, codes::TYPE_DURATION => Type::Duration, codes::TYPE_SYSTEM_TIME => Type::Timestamp, - codes::TYPE_FOREIGN_EXECUTOR => Type::ForeignExecutor, codes::TYPE_RECORD => Type::Record { module_path: self.read_string()?, name: self.read_string()?, @@ -134,7 +156,17 @@ impl<'a> MetadataReader<'a> { codes::TYPE_INTERFACE => Type::Object { module_path: self.read_string()?, name: self.read_string()?, - imp: ObjectImpl::from_is_trait(self.read_bool()?), + imp: ObjectImpl::Struct, + }, + codes::TYPE_TRAIT_INTERFACE => Type::Object { + module_path: self.read_string()?, + name: self.read_string()?, + imp: ObjectImpl::Trait, + }, + codes::TYPE_CALLBACK_TRAIT_INTERFACE => Type::Object { + module_path: self.read_string()?, + name: self.read_string()?, + imp: ObjectImpl::CallbackTrait, }, codes::TYPE_CALLBACK_INTERFACE => Type::CallbackInterface { module_path: self.read_string()?, @@ -198,6 +230,7 @@ impl<'a> MetadataReader<'a> { let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_long_string()?; Ok(FnMetadata { module_path, name, @@ -205,6 +238,7 @@ impl<'a> MetadataReader<'a> { inputs, return_type, throws, + docstring, checksum: self.calc_checksum(), }) } @@ -213,8 +247,10 @@ impl<'a> MetadataReader<'a> { let module_path = self.read_string()?; let self_name = self.read_string()?; let name = self.read_string()?; + let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_long_string()?; return_type .filter(|t| { @@ -228,10 +264,12 @@ impl<'a> MetadataReader<'a> { Ok(ConstructorMetadata { module_path, self_name, + is_async, name, inputs, throws, checksum: self.calc_checksum(), + docstring, }) } @@ -242,6 +280,7 @@ impl<'a> MetadataReader<'a> { let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_long_string()?; Ok(MethodMetadata { module_path, self_name, @@ -252,6 +291,7 @@ impl<'a> MetadataReader<'a> { throws, takes_self_by_arc: false, // not emitted by macros checksum: self.calc_checksum(), + docstring, }) } @@ -260,13 +300,25 @@ impl<'a> MetadataReader<'a> { module_path: self.read_string()?, name: self.read_string()?, fields: self.read_fields()?, + docstring: self.read_optional_long_string()?, }) } - fn read_enum(&mut self, is_flat_error: bool) -> Result<EnumMetadata> { + fn read_enum(&mut self) -> Result<EnumMetadata> { let module_path = self.read_string()?; let name = self.read_string()?; - let variants = if is_flat_error { + let forced_flatness = match self.read_u8()? { + 0 => None, + 1 => Some(false), + 2 => Some(true), + _ => unreachable!("invalid flatness"), + }; + let discr_type = if self.read_bool()? { + Some(self.read_type()?) + } else { + None + }; + let variants = if forced_flatness == Some(true) { self.read_flat_variants()? } else { self.read_variants()? @@ -275,21 +327,20 @@ impl<'a> MetadataReader<'a> { Ok(EnumMetadata { module_path, name, + forced_flatness, + discr_type, variants, + non_exhaustive: self.read_bool()?, + docstring: self.read_optional_long_string()?, }) } - fn read_error(&mut self) -> Result<ErrorMetadata> { - let is_flat = self.read_bool()?; - let enum_ = self.read_enum(is_flat)?; - Ok(ErrorMetadata::Enum { enum_, is_flat }) - } - - fn read_object(&mut self) -> Result<ObjectMetadata> { + fn read_object(&mut self, imp: ObjectImpl) -> Result<ObjectMetadata> { Ok(ObjectMetadata { module_path: self.read_string()?, name: self.read_string()?, - imp: ObjectImpl::from_is_trait(self.read_bool()?), + imp, + docstring: self.read_optional_long_string()?, }) } @@ -322,6 +373,7 @@ impl<'a> MetadataReader<'a> { Ok(CallbackInterfaceMetadata { module_path: self.read_string()?, name: self.read_string()?, + docstring: self.read_optional_long_string()?, }) } @@ -333,6 +385,7 @@ impl<'a> MetadataReader<'a> { let is_async = self.read_bool()?; let inputs = self.read_inputs()?; let (return_type, throws) = self.read_return_type()?; + let docstring = self.read_optional_long_string()?; Ok(TraitMethodMetadata { module_path, trait_name, @@ -344,6 +397,7 @@ impl<'a> MetadataReader<'a> { throws, takes_self_by_arc: false, // not emitted by macros checksum: self.calc_checksum(), + docstring, }) } @@ -353,8 +407,13 @@ impl<'a> MetadataReader<'a> { .map(|_| { let name = self.read_string()?; let ty = self.read_type()?; - let default = self.read_default(&name, &ty)?; - Ok(FieldMetadata { name, ty, default }) + let default = self.read_optional_default(&name, &ty)?; + Ok(FieldMetadata { + name, + ty, + default, + docstring: self.read_optional_long_string()?, + }) }) .collect() } @@ -365,7 +424,9 @@ impl<'a> MetadataReader<'a> { .map(|_| { Ok(VariantMetadata { name: self.read_string()?, + discr: self.read_optional_default("<variant-value>", &Type::UInt64)?, fields: self.read_fields()?, + docstring: self.read_optional_long_string()?, }) }) .collect() @@ -377,7 +438,9 @@ impl<'a> MetadataReader<'a> { .map(|_| { Ok(VariantMetadata { name: self.read_string()?, + discr: None, fields: vec![], + docstring: self.read_optional_long_string()?, }) }) .collect() @@ -387,13 +450,16 @@ impl<'a> MetadataReader<'a> { let len = self.read_u8()?; (0..len) .map(|_| { + let name = self.read_string()?; + let ty = self.read_type()?; + let default = self.read_optional_default(&name, &ty)?; Ok(FnParamMetadata { - name: self.read_string()?, - ty: self.read_type()?, + name, + ty, + default, // not emitted by macros by_ref: false, optional: false, - default: None, }) }) .collect() @@ -405,14 +471,18 @@ impl<'a> MetadataReader<'a> { Some(checksum_metadata(metadata_buf)) } - fn read_default(&mut self, name: &str, ty: &Type) -> Result<Option<LiteralMetadata>> { - let has_default = self.read_bool()?; - if !has_default { - return Ok(None); + fn read_optional_default(&mut self, name: &str, ty: &Type) -> Result<Option<LiteralMetadata>> { + if self.read_bool()? { + Ok(Some(self.read_default(name, ty)?)) + } else { + Ok(None) } + } + fn read_default(&mut self, name: &str, ty: &Type) -> Result<LiteralMetadata> { let literal_kind = self.read_u8()?; - Ok(Some(match literal_kind { + + Ok(match literal_kind { codes::LIT_STR => { ensure!( matches!(ty, Type::String), @@ -422,12 +492,24 @@ impl<'a> MetadataReader<'a> { } codes::LIT_INT => { let base10_digits = self.read_string()?; + // procmacros emit the type for discriminant values based purely on whether the constant + // is positive or negative. + let ty = if !base10_digits.is_empty() + && base10_digits.as_bytes()[0] == b'-' + && ty == &Type::UInt64 + { + &Type::Int64 + } else { + ty + }; macro_rules! parse_int { ($ty:ident, $variant:ident) => { LiteralMetadata::$variant( base10_digits .parse::<$ty>() - .with_context(|| format!("parsing default for field {name}"))? + .with_context(|| { + format!("parsing default for field {name}: {base10_digits}") + })? .into(), Radix::Decimal, ty.to_owned(), @@ -458,8 +540,18 @@ impl<'a> MetadataReader<'a> { } }, codes::LIT_BOOL => LiteralMetadata::Boolean(self.read_bool()?), - codes::LIT_NULL => LiteralMetadata::Null, + codes::LIT_NONE => match ty { + Type::Optional { .. } => LiteralMetadata::None, + _ => bail!("field {name} of type {ty:?} can't have a default value of None"), + }, + codes::LIT_SOME => match ty { + Type::Optional { inner_type, .. } => LiteralMetadata::Some { + inner: Box::new(self.read_default(name, inner_type)?), + }, + _ => bail!("field {name} of type {ty:?} can't have a default value of None"), + }, + codes::LIT_EMPTY_SEQ => LiteralMetadata::EmptySequence, _ => bail!("Unexpected literal kind code: {literal_kind:?}"), - })) + }) } } diff --git a/third_party/rust/uniffi_meta/src/types.rs b/third_party/rust/uniffi_meta/src/types.rs index 24f8a6f2a8..51bf156b50 100644 --- a/third_party/rust/uniffi_meta/src/types.rs +++ b/third_party/rust/uniffi_meta/src/types.rs @@ -21,8 +21,12 @@ use crate::Checksum; #[derive(Debug, Copy, Clone, Eq, PartialEq, Checksum, Ord, PartialOrd)] pub enum ObjectImpl { + // A single Rust type Struct, + // A trait that's can be implemented by Rust types Trait, + // A trait + a callback interface -- can be implemented by both Rust and foreign types. + CallbackTrait, } impl ObjectImpl { @@ -31,27 +35,26 @@ impl ObjectImpl { /// Includes `r#`, traits get a leading `dyn`. If we ever supported associated types, then /// this would also include them. pub fn rust_name_for(&self, name: &str) -> String { - if self == &ObjectImpl::Trait { + if self.is_trait_interface() { format!("dyn r#{name}") } else { format!("r#{name}") } } - // uniffi_meta and procmacro support tend to carry around `is_trait` bools. This makes that - // mildly less painful - pub fn from_is_trait(is_trait: bool) -> Self { - if is_trait { - ObjectImpl::Trait - } else { - ObjectImpl::Struct - } + pub fn is_trait_interface(&self) -> bool { + matches!(self, Self::Trait | Self::CallbackTrait) + } + + pub fn has_callback_interface(&self) -> bool { + matches!(self, Self::CallbackTrait) } } #[derive(Debug, Clone, Copy, Eq, PartialEq, Checksum, Ord, PartialOrd)] pub enum ExternalKind { Interface, + Trait, // Either a record or enum DataClass, } @@ -85,7 +88,6 @@ pub enum Type { // How the object is implemented. imp: ObjectImpl, }, - ForeignExecutor, // Types defined in the component API, each of which has a string name. Record { module_path: String, diff --git a/third_party/rust/uniffi_testing/.cargo-checksum.json b/third_party/rust/uniffi_testing/.cargo-checksum.json index 0af9b557d8..47b58d8bcf 100644 --- a/third_party/rust/uniffi_testing/.cargo-checksum.json +++ b/third_party/rust/uniffi_testing/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"300889e9b31b2f73133d69a8c5f8854c72cefd398b4f656c7fc06f4c46475dcc","README.md":"ec6aba24af9a011ef6647422aa22efabdee519cdee3da1a9f9033b07b7cbdb0d","src/lib.rs":"e19f60aed5a137401203b9054ead27894b98490bab3e2f680a23549c8ee8a13a"},"package":"118448debffcb676ddbe8c5305fb933ab7e0123753e659a71dc4a693f8d9f23c"}
\ No newline at end of file +{"files":{"Cargo.toml":"f29ebbc363e01ee31c5a351aa6c19bc99343b4d293d5ea7954bca3f49e77ad54","README.md":"ec6aba24af9a011ef6647422aa22efabdee519cdee3da1a9f9033b07b7cbdb0d","src/lib.rs":"e19f60aed5a137401203b9054ead27894b98490bab3e2f680a23549c8ee8a13a"},"package":"f8ce878d0bdfc288b58797044eaaedf748526c56eef3575380bb4d4b19d69eee"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_testing/Cargo.toml b/third_party/rust/uniffi_testing/Cargo.toml index a4f6f0bf54..5dacf0cf31 100644 --- a/third_party/rust/uniffi_testing/Cargo.toml +++ b/third_party/rust/uniffi_testing/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2021" name = "uniffi_testing" -version = "0.25.3" +version = "0.27.1" authors = ["Firefox Sync Team <sync-team@mozilla.com>"] description = "a multi-language bindings generator for rust (testing helpers)" homepage = "https://mozilla.github.io/uniffi-rs" diff --git a/third_party/rust/uniffi_udl/.cargo-checksum.json b/third_party/rust/uniffi_udl/.cargo-checksum.json index 233a1e7795..88ed1cb4b7 100644 --- a/third_party/rust/uniffi_udl/.cargo-checksum.json +++ b/third_party/rust/uniffi_udl/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"8bb1bcb6089114c92a0d06f884e64769edc493bd26d271eb659d6592d8d2f5fa","src/attributes.rs":"faf1c53c17ff511f587113afdb37de4890087150f32e19c9af4d878daebe7c89","src/collectors.rs":"3d80f169ccb7a64c434a6890948e49a64947062c29487b5bea58721189cb2786","src/converters/callables.rs":"c41d15cf816f5838e74f0c1d7a5806c4f1c1b2d925ca11b2b6658a978b2373fb","src/converters/enum_.rs":"1742441b812949b83ec28c2c2c4684c34fd618195cff47af2934c7ee9c895c59","src/converters/interface.rs":"a3deb1915eb4be80d21116f24bec7163736bdffbad4eb2236fb531386a97c4f8","src/converters/mod.rs":"4035ec70c56917fa7c61cced5f8a8398c99028e13959662acc1f7a48aa71ae3b","src/finder.rs":"c496aca0ac640d4ca5fd57b32131297544e5468c9e792f8b5da1644df6d22d2e","src/lib.rs":"11a5d3c288f5786164471b0870b5ca9190305757461c6a6d20777a96622457f1","src/literal.rs":"30e50d7c1d3f061bb6aaa7ad3a6eb31d75a6ea21159dfe454f7ab9ae4bdb580e","src/resolver.rs":"96212b52c4f4f7637713d0a39f5c4972e6480e4ce98070dcb8647d67c9b8d5e2"},"package":"889edb7109c6078abe0e53e9b4070cf74a6b3468d141bdf5ef1bd4d1dc24a1c3"}
\ No newline at end of file +{"files":{"Cargo.toml":"e4553df91528daadbc52244f436c3ae17d9c6a4629d232643a20ed63c1311625","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/attributes.rs":"f1cdee01db837920dbd109d60b39dd4b0ece56f83797fe4ace1e0caf97c02483","src/collectors.rs":"00c9cd8e8f7be6949996f069b449f120f0b0694ff2f1ca92b79201721c18c594","src/converters/callables.rs":"1ad26c2782629e98272edc75c38343f58194ebf9626ae451ba84546a60d45a48","src/converters/enum_.rs":"aa0ca7a7a50521a45e296c6f53be5f1981a41872443946f72c2ca8baebe4d69b","src/converters/interface.rs":"6042d4abd5e236ed6158c5f6d4d9b81bb01fbbcb8b42bf8a5b385b978c68f6f8","src/converters/mod.rs":"c34a30e3b7a2e3c092a7074f4bab6f6c34364177c096af59a7dda13e44ffdc3c","src/finder.rs":"3586ffd3772151eabbc3d6062725933c88978e1b5feb64312bbf42cd43af30fa","src/lib.rs":"56c50ce61ba5e7266fe7fc9fa9d0022cdbfbe9801730753bd4ee66fed040221c","src/literal.rs":"4f28ad49a17246b4dc30a677cfff65b345bfac0924856e19f58e7574a74c2c40","src/resolver.rs":"c4ff362055dc4ed489e94a063ec0fd3becb8787ad35ce65cdec437a1aae518a0"},"package":"8c43c9ed40a8d20a5c3eae2d23031092db6b96dc8e571beb449ba9757484cea0"}
\ No newline at end of file diff --git a/third_party/rust/uniffi_udl/Cargo.toml b/third_party/rust/uniffi_udl/Cargo.toml index 346cd71c27..fd03f3434a 100644 --- a/third_party/rust/uniffi_udl/Cargo.toml +++ b/third_party/rust/uniffi_udl/Cargo.toml @@ -12,10 +12,11 @@ [package] edition = "2021" name = "uniffi_udl" -version = "0.25.3" +version = "0.27.1" description = "udl parsing for the uniffi project" homepage = "https://mozilla.github.io/uniffi-rs" documentation = "https://mozilla.github.io/uniffi-rs" +readme = "README.md" keywords = [ "ffi", "bindgen", @@ -26,11 +27,14 @@ repository = "https://github.com/mozilla/uniffi-rs" [dependencies.anyhow] version = "1" +[dependencies.textwrap] +version = "0.16" + [dependencies.uniffi_meta] -version = "=0.25.3" +version = "=0.27.1" [dependencies.uniffi_testing] -version = "=0.25.3" +version = "=0.27.1" [dependencies.weedle2] -version = "4.0.0" +version = "5.0.0" diff --git a/third_party/rust/uniffi_udl/README.md b/third_party/rust/uniffi_udl/README.md new file mode 100644 index 0000000000..64ac3486a3 --- /dev/null +++ b/third_party/rust/uniffi_udl/README.md @@ -0,0 +1,81 @@ +# UniFFI - a multi-language bindings generator for Rust + +UniFFI is a toolkit for building cross-platform software components in Rust. + +For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/) +or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components). + +By writing your core business logic in Rust and describing its interface in an "object model", +you can use UniFFI to help you: + +* Compile your Rust code into a shared library for use on different target platforms. +* Generate bindings to load and use the library from different target languages. + +You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html) +or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html). + +UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers; +written once in Rust, auto-generated bindings allow that functionality to be called +from both Kotlin (for Android apps) and Swift (for iOS apps). +It also has a growing community of users shipping various cool things to many users. + +UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**. +Additional foreign language bindings can be developed externally and we welcome contributions to list them here. +See [Third-party foreign language bindings](#third-party-foreign-language-bindings). + +## User Guide + +You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/). + +We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on. +We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time. + +### Etymology and Pronunciation + +ˈjuːnɪfaɪ. Pronounced to rhyme with "unify". + +A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages. + +uni - [Latin ūni-, from ūnus, one] +FFI - [Abbreviation, Foreign Function Interface] + +## Alternative tools + +Other tools we know of which try and solve a similarly shaped problem are: + +* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of + the different approach taken by that tool](docs/diplomat-and-macros.md) +* [Interoptopus](https://github.com/ralfbiedert/interoptopus/) + +(Please open a PR if you think other tools should be listed!) + +## Third-party foreign language bindings + +* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native. +* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go) +* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs) +* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart) + +### External resources + +There are a few third-party resources that make it easier to work with UniFFI: + +* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/). +* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts. +* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful. +* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure. + +(Please open a PR if you think other resources should be listed!) + +## Contributing + +If this tool sounds interesting to you, please help us develop it! You can: + +* View the [contributor guidelines](./docs/contributing.md). +* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub. +* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org) + room on Matrix. + +## Code of Conduct + +This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md). diff --git a/third_party/rust/uniffi_udl/src/attributes.rs b/third_party/rust/uniffi_udl/src/attributes.rs index f06b4f29c1..7bb05808c4 100644 --- a/third_party/rust/uniffi_udl/src/attributes.rs +++ b/third_party/rust/uniffi_udl/src/attributes.rs @@ -37,10 +37,29 @@ pub(super) enum Attribute { kind: ExternalKind, export: bool, }, + Rust { + kind: RustKind, + }, // Custom type on the scaffolding side Custom, // The interface described is implemented as a trait. Trait, + // Modifies `Trait` to enable foreign implementations (callback interfaces) + WithForeign, + Async, + NonExhaustive, +} + +// A type defined in Rust via procmacros but which should be available +// in UDL. +#[derive(Debug, Copy, Clone, Checksum)] +pub(super) enum RustKind { + Object, + CallbackTrait, + Trait, + Record, + Enum, + CallbackInterface, } impl Attribute { @@ -67,6 +86,9 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute { "Error" => Ok(Attribute::Error), "Custom" => Ok(Attribute::Custom), "Trait" => Ok(Attribute::Trait), + "WithForeign" => Ok(Attribute::WithForeign), + "Async" => Ok(Attribute::Async), + "NonExhaustive" => Ok(Attribute::NonExhaustive), _ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0), }, // Matches assignment-style attributes like ["Throws=Error"] @@ -95,6 +117,19 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute { kind: ExternalKind::Interface, export: true, }), + "ExternalTrait" => Ok(Attribute::External { + crate_name: name_from_id_or_string(&identity.rhs), + kind: ExternalKind::Trait, + export: false, + }), + "ExternalTraitExport" => Ok(Attribute::External { + crate_name: name_from_id_or_string(&identity.rhs), + kind: ExternalKind::Trait, + export: true, + }), + "Rust" => Ok(Attribute::Rust { + kind: rust_kind_from_id_or_string(&identity.rhs)?, + }), _ => anyhow::bail!( "Attribute identity Identifier not supported: {:?}", identity.lhs_identifier.0 @@ -130,6 +165,26 @@ fn name_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> Str } } +fn rust_kind_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> Result<RustKind> { + Ok(match nm { + weedle::attribute::IdentifierOrString::String(str_lit) => match str_lit.0 { + // support names which match either procmacro or udl + "interface" => RustKind::Object, + "object" => RustKind::Object, + "record" => RustKind::Record, + "dictionary" => RustKind::Record, + "enum" => RustKind::Enum, + "trait" => RustKind::Trait, + "callback" => RustKind::CallbackInterface, + "trait_with_foreign" => RustKind::CallbackTrait, + _ => anyhow::bail!("Unknown `[Rust=]` kind {:?}", str_lit.0), + }, + weedle::attribute::IdentifierOrString::Identifier(_) => { + anyhow::bail!("Expected string attribute value, got identifier") + } + }) +} + /// Parse a weedle `ExtendedAttributeList` into a list of `Attribute`s, /// erroring out on duplicates. fn parse_attributes<F>( @@ -161,7 +216,6 @@ where } /// Attributes that can be attached to an `enum` definition in the UDL. -/// There's only one case here: using `[Error]` to mark an enum as an error class. #[derive(Debug, Clone, Checksum, Default)] pub(super) struct EnumAttributes(Vec<Attribute>); @@ -169,6 +223,12 @@ impl EnumAttributes { pub fn contains_error_attr(&self) -> bool { self.0.iter().any(|attr| attr.is_error()) } + + pub fn contains_non_exhaustive_attr(&self) -> bool { + self.0 + .iter() + .any(|attr| matches!(attr, Attribute::NonExhaustive)) + } } impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes { @@ -178,6 +238,10 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes { ) -> Result<Self, Self::Error> { let attrs = parse_attributes(weedle_attributes, |attr| match attr { Attribute::Error => Ok(()), + Attribute::NonExhaustive => Ok(()), + // Allow `[Enum]`, since we may be parsing an attribute list from an interface with the + // `[Enum]` attribute. + Attribute::Enum => Ok(()), _ => bail!(format!("{attr:?} not supported for enums")), })?; Ok(Self(attrs)) @@ -196,8 +260,9 @@ impl<T: TryInto<EnumAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for E /// Represents UDL attributes that might appear on a function. /// -/// This supports the `[Throws=ErrorName]` attribute for functions that -/// can produce an error. +/// This supports: +/// * `[Throws=ErrorName]` attribute for functions that can produce an error. +/// * `[Async] for async functions #[derive(Debug, Clone, Checksum, Default)] pub(super) struct FunctionAttributes(Vec<Attribute>); @@ -210,6 +275,10 @@ impl FunctionAttributes { _ => None, }) } + + pub(super) fn is_async(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Async)) + } } impl FromIterator<Attribute> for FunctionAttributes { @@ -224,7 +293,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttribut weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, ) -> Result<Self, Self::Error> { let attrs = parse_attributes(weedle_attributes, |attr| match attr { - Attribute::Throws(_) => Ok(()), + Attribute::Throws(_) | Attribute::Async => Ok(()), _ => bail!(format!("{attr:?} not supported for functions")), })?; Ok(Self(attrs)) @@ -294,12 +363,25 @@ impl InterfaceAttributes { self.0.iter().any(|attr| attr.is_error()) } - pub fn object_impl(&self) -> ObjectImpl { - if self.0.iter().any(|attr| matches!(attr, Attribute::Trait)) { - ObjectImpl::Trait - } else { - ObjectImpl::Struct - } + pub fn contains_trait(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Trait)) + } + + pub fn contains_with_foreign(&self) -> bool { + self.0 + .iter() + .any(|attr| matches!(attr, Attribute::WithForeign)) + } + + pub fn object_impl(&self) -> Result<ObjectImpl> { + Ok( + match (self.contains_trait(), self.contains_with_foreign()) { + (true, true) => ObjectImpl::CallbackTrait, + (true, false) => ObjectImpl::Trait, + (false, false) => ObjectImpl::Struct, + (false, true) => bail!("WithForeign can't be specified without Trait"), + }, + ) } pub fn get_traits(&self) -> Vec<String> { self.0 @@ -321,6 +403,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for InterfaceAttribu Attribute::Enum => Ok(()), Attribute::Error => Ok(()), Attribute::Trait => Ok(()), + Attribute::WithForeign => Ok(()), Attribute::Traits(_) => Ok(()), _ => bail!(format!("{attr:?} not supported for interface definition")), })?; @@ -373,6 +456,10 @@ impl ConstructorAttributes { _ => None, }) } + + pub(super) fn is_async(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Async)) + } } impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ConstructorAttributes { @@ -383,6 +470,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ConstructorAttri let attrs = parse_attributes(weedle_attributes, |attr| match attr { Attribute::Throws(_) => Ok(()), Attribute::Name(_) => Ok(()), + Attribute::Async => Ok(()), _ => bail!(format!("{attr:?} not supported for constructors")), })?; Ok(Self(attrs)) @@ -406,6 +494,10 @@ impl MethodAttributes { }) } + pub(super) fn is_async(&self) -> bool { + self.0.iter().any(|attr| matches!(attr, Attribute::Async)) + } + pub(super) fn get_self_by_arc(&self) -> bool { self.0 .iter() @@ -425,8 +517,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for MethodAttributes weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, ) -> Result<Self, Self::Error> { let attrs = parse_attributes(weedle_attributes, |attr| match attr { - Attribute::SelfType(_) => Ok(()), - Attribute::Throws(_) => Ok(()), + Attribute::SelfType(_) | Attribute::Throws(_) | Attribute::Async => Ok(()), _ => bail!(format!("{attr:?} not supported for methods")), })?; Ok(Self(attrs)) @@ -498,10 +589,18 @@ impl TypedefAttributes { }) } + pub(super) fn rust_kind(&self) -> Option<RustKind> { + self.0.iter().find_map(|attr| match attr { + Attribute::Rust { kind, .. } => Some(*kind), + _ => None, + }) + } + pub(super) fn external_tagged(&self) -> Option<bool> { // If it was "exported" via a proc-macro the FfiConverter was not tagged. self.0.iter().find_map(|attr| match attr { Attribute::External { export, .. } => Some(!*export), + Attribute::Rust { .. } => Some(false), _ => None, }) } @@ -513,7 +612,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for TypedefAttribute weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>, ) -> Result<Self, Self::Error> { let attrs = parse_attributes(weedle_attributes, |attr| match attr { - Attribute::External { .. } | Attribute::Custom => Ok(()), + Attribute::External { .. } | Attribute::Custom | Attribute::Rust { .. } => Ok(()), _ => bail!(format!("{attr:?} not supported for typedefs")), })?; Ok(Self(attrs)) @@ -641,14 +740,22 @@ mod test { } #[test] - fn test_throws_attribute() { + fn test_function_attributes() { let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap(); let attrs = FunctionAttributes::try_from(&node).unwrap(); assert!(matches!(attrs.get_throws_err(), Some("Error"))); + assert!(!attrs.is_async()); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); let attrs = FunctionAttributes::try_from(&node).unwrap(); assert!(attrs.get_throws_err().is_none()); + assert!(!attrs.is_async()); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Async]").unwrap(); + let attrs = FunctionAttributes::try_from(&node).unwrap(); + assert!(matches!(attrs.get_throws_err(), Some("Error"))); + assert!(attrs.is_async()); } #[test] @@ -673,22 +780,34 @@ mod test { let attrs = MethodAttributes::try_from(&node).unwrap(); assert!(!attrs.get_self_by_arc()); assert!(matches!(attrs.get_throws_err(), Some("Error"))); + assert!(!attrs.is_async()); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); let attrs = MethodAttributes::try_from(&node).unwrap(); assert!(!attrs.get_self_by_arc()); assert!(attrs.get_throws_err().is_none()); + assert!(!attrs.is_async()); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error]").unwrap(); let attrs = MethodAttributes::try_from(&node).unwrap(); assert!(attrs.get_self_by_arc()); assert!(attrs.get_throws_err().is_some()); + assert!(!attrs.is_async()); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error, Async]") + .unwrap(); + let attrs = MethodAttributes::try_from(&node).unwrap(); + assert!(attrs.get_self_by_arc()); + assert!(attrs.get_throws_err().is_some()); + assert!(attrs.is_async()); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc]").unwrap(); let attrs = MethodAttributes::try_from(&node).unwrap(); assert!(attrs.get_self_by_arc()); assert!(attrs.get_throws_err().is_none()); + assert!(!attrs.is_async()); } #[test] @@ -710,6 +829,11 @@ mod test { let attrs = ConstructorAttributes::try_from(&node).unwrap(); assert!(matches!(attrs.get_throws_err(), Some("Error"))); assert!(matches!(attrs.get_name(), Some("MyFactory"))); + assert!(!attrs.is_async()); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Async]").unwrap(); + let attrs = ConstructorAttributes::try_from(&node).unwrap(); + assert!(attrs.is_async()); } #[test] @@ -754,15 +878,24 @@ mod test { fn test_trait_attribute() { let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap(); let attrs = InterfaceAttributes::try_from(&node).unwrap(); - assert_eq!(attrs.object_impl(), ObjectImpl::Trait); + assert_eq!(attrs.object_impl().unwrap(), ObjectImpl::Trait); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Trait, WithForeign]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert_eq!(attrs.object_impl().unwrap(), ObjectImpl::CallbackTrait); let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap(); let attrs = InterfaceAttributes::try_from(&node).unwrap(); - assert_eq!(attrs.object_impl(), ObjectImpl::Struct); + assert_eq!(attrs.object_impl().unwrap(), ObjectImpl::Struct); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[WithForeign]").unwrap(); + let attrs = InterfaceAttributes::try_from(&node).unwrap(); + assert!(attrs.object_impl().is_err()) } #[test] - fn test_enum_attribute() { + fn test_enum_attribute_on_interface() { let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap(); let attrs = InterfaceAttributes::try_from(&node).unwrap(); assert!(matches!(attrs.contains_enum_attr(), true)); @@ -783,6 +916,38 @@ mod test { ); } + // Test parsing attributes for enum definitions + #[test] + fn test_enum_attributes() { + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Error, NonExhaustive]").unwrap(); + let attrs = EnumAttributes::try_from(&node).unwrap(); + assert!(attrs.contains_error_attr()); + assert!(attrs.contains_non_exhaustive_attr()); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap(); + let err = EnumAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "Trait not supported for enums"); + } + + // Test parsing attributes for interface definitions with the `[Enum]` attribute + #[test] + fn test_enum_attributes_from_interface() { + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap(); + assert!(EnumAttributes::try_from(&node).is_ok()); + + let (_, node) = + weedle::attribute::ExtendedAttributeList::parse("[Enum, Error, NonExhaustive]") + .unwrap(); + let attrs = EnumAttributes::try_from(&node).unwrap(); + assert!(attrs.contains_error_attr()); + assert!(attrs.contains_non_exhaustive_attr()); + + let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum, Trait]").unwrap(); + let err = EnumAttributes::try_from(&node).unwrap_err(); + assert_eq!(err.to_string(), "Trait not supported for enums"); + } + #[test] fn test_other_attributes_not_supported_for_interfaces() { let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait, ByRef]").unwrap(); diff --git a/third_party/rust/uniffi_udl/src/collectors.rs b/third_party/rust/uniffi_udl/src/collectors.rs index 6a91ab4a93..de5489f5f9 100644 --- a/third_party/rust/uniffi_udl/src/collectors.rs +++ b/third_party/rust/uniffi_udl/src/collectors.rs @@ -5,7 +5,7 @@ //! # Collects metadata from UDL. use crate::attributes; -use crate::converters::APIConverter; +use crate::converters::{convert_docstring, APIConverter}; use crate::finder; use crate::resolver::TypeResolver; use anyhow::{bail, Result}; @@ -138,6 +138,7 @@ impl From<InterfaceCollector> for uniffi_meta::MetadataGroup { crate_name: value.types.module_path(), name: value.types.namespace, }, + namespace_docstring: value.types.namespace_docstring.clone(), items: value.items, } } @@ -171,15 +172,13 @@ impl APIBuilder for weedle::Definition<'_> { match self { weedle::Definition::Namespace(d) => d.process(ci)?, weedle::Definition::Enum(d) => { + let mut e: uniffi_meta::EnumMetadata = d.convert(ci)?; // We check if the enum represents an error... let attrs = attributes::EnumAttributes::try_from(d.attributes.as_ref())?; if attrs.contains_error_attr() { - let e: uniffi_meta::ErrorMetadata = d.convert(ci)?; - ci.add_definition(e.into())?; - } else { - let e: uniffi_meta::EnumMetadata = d.convert(ci)?; - ci.add_definition(e.into())?; + e.forced_flatness = Some(true); } + ci.add_definition(e.into())?; } weedle::Definition::Dictionary(d) => { let rec = d.convert(ci)?; @@ -187,12 +186,9 @@ impl APIBuilder for weedle::Definition<'_> { } weedle::Definition::Interface(d) => { let attrs = attributes::InterfaceAttributes::try_from(d.attributes.as_ref())?; - if attrs.contains_enum_attr() { + if attrs.contains_enum_attr() || attrs.contains_error_attr() { let e: uniffi_meta::EnumMetadata = d.convert(ci)?; ci.add_definition(e.into())?; - } else if attrs.contains_error_attr() { - let e: uniffi_meta::ErrorMetadata = d.convert(ci)?; - ci.add_definition(e.into())?; } else { let obj: uniffi_meta::ObjectMetadata = d.convert(ci)?; ci.add_definition(obj.into())?; @@ -218,6 +214,7 @@ impl APIBuilder for weedle::NamespaceDefinition<'_> { if self.identifier.0 != ci.types.namespace { bail!("duplicate namespace definition"); } + ci.types.namespace_docstring = self.docstring.as_ref().map(|v| convert_docstring(&v.0)); for func in self.members.body.convert(ci)? { ci.add_definition(func.into())?; } @@ -229,6 +226,7 @@ impl APIBuilder for weedle::NamespaceDefinition<'_> { pub(crate) struct TypeCollector { /// The unique prefix that we'll use for namespacing when exposing this component's API. pub namespace: String, + pub namespace_docstring: Option<String>, pub crate_name: String, diff --git a/third_party/rust/uniffi_udl/src/converters/callables.rs b/third_party/rust/uniffi_udl/src/converters/callables.rs index 3e15bb8e02..dda3c3a3ce 100644 --- a/third_party/rust/uniffi_udl/src/converters/callables.rs +++ b/third_party/rust/uniffi_udl/src/converters/callables.rs @@ -5,6 +5,7 @@ use super::APIConverter; use crate::attributes::ArgumentAttributes; use crate::attributes::{ConstructorAttributes, FunctionAttributes, MethodAttributes}; +use crate::converters::convert_docstring; use crate::literal::convert_default_value; use crate::InterfaceCollector; use anyhow::{bail, Result}; @@ -41,6 +42,7 @@ impl APIConverter<FieldMetadata> for weedle::argument::SingleArgument<'_> { name: self.identifier.0.to_string(), ty: type_, default: None, + docstring: None, }) } } @@ -89,6 +91,7 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_ Some(id) => id.0.to_string(), }; let attrs = FunctionAttributes::try_from(self.attributes.as_ref())?; + let is_async = attrs.is_async(); let throws = match attrs.get_throws_err() { None => None, Some(name) => match ci.get_type(name) { @@ -99,10 +102,11 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_ Ok(FnMetadata { module_path: ci.module_path(), name, - is_async: false, + is_async, return_type, inputs: self.args.body.list.convert(ci)?, throws, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), checksum: None, }) } @@ -122,10 +126,12 @@ impl APIConverter<ConstructorMetadata> for weedle::interface::ConstructorInterfa name: String::from(attributes.get_name().unwrap_or("new")), // We don't know the name of the containing `Object` at this point, fill it in later. self_name: Default::default(), + is_async: attributes.is_async(), // Also fill in checksum_fn_name later, since it depends on object_name inputs: self.args.body.list.convert(ci)?, throws, checksum: None, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -140,6 +146,7 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe } let return_type = ci.resolve_return_type_expression(&self.return_type)?; let attributes = MethodAttributes::try_from(self.attributes.as_ref())?; + let is_async = attributes.is_async(); let throws = match attributes.get_throws_err() { Some(name) => match ci.get_type(name) { @@ -164,12 +171,13 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe }, // We don't know the name of the containing `Object` at this point, fill it in later. self_name: Default::default(), - is_async: false, // not supported in UDL + is_async, inputs: self.args.body.list.convert(ci)?, return_type, throws, takes_self_by_arc, checksum: None, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -184,6 +192,7 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface } let return_type = ci.resolve_return_type_expression(&self.return_type)?; let attributes = MethodAttributes::try_from(self.attributes.as_ref())?; + let is_async = attributes.is_async(); let throws = match attributes.get_throws_err() { Some(name) => match ci.get_type(name) { @@ -208,12 +217,13 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface name } }, - is_async: false, // not supported in udl + is_async, inputs: self.args.body.list.convert(ci)?, return_type, throws, takes_self_by_arc, checksum: None, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } diff --git a/third_party/rust/uniffi_udl/src/converters/enum_.rs b/third_party/rust/uniffi_udl/src/converters/enum_.rs index a3e68fd23e..1615a1a7ca 100644 --- a/third_party/rust/uniffi_udl/src/converters/enum_.rs +++ b/third_party/rust/uniffi_udl/src/converters/enum_.rs @@ -3,19 +3,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::APIConverter; -use crate::InterfaceCollector; +use crate::{attributes::EnumAttributes, converters::convert_docstring, InterfaceCollector}; use anyhow::{bail, Result}; -use uniffi_meta::{EnumMetadata, ErrorMetadata, VariantMetadata}; +use uniffi_meta::{EnumMetadata, VariantMetadata}; -// Note that we have four `APIConverter` impls here - one for the `enum` case, -// one for the `[Error] enum` case, and and one for the `[Enum] interface` case, -// and one for the `[Error] interface` case. +// Note that we have 2 `APIConverter` impls here - one for the `enum` case +// (including an enum with `[Error]`), and one for the `[Error] interface` cas +// (which is still an enum, but with different "flatness" characteristics.) impl APIConverter<EnumMetadata> for weedle::EnumDefinition<'_> { fn convert(&self, ci: &mut InterfaceCollector) -> Result<EnumMetadata> { + let attributes = EnumAttributes::try_from(self.attributes.as_ref())?; Ok(EnumMetadata { module_path: ci.module_path(), name: self.identifier.0.to_string(), + forced_flatness: None, + discr_type: None, variants: self .values .body @@ -23,35 +26,15 @@ impl APIConverter<EnumMetadata> for weedle::EnumDefinition<'_> { .iter() .map::<Result<_>, _>(|v| { Ok(VariantMetadata { - name: v.0.to_string(), + name: v.value.0.to_string(), + discr: None, fields: vec![], + docstring: v.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) }) .collect::<Result<Vec<_>>>()?, - }) - } -} - -impl APIConverter<ErrorMetadata> for weedle::EnumDefinition<'_> { - fn convert(&self, ci: &mut InterfaceCollector) -> Result<ErrorMetadata> { - Ok(ErrorMetadata::Enum { - enum_: EnumMetadata { - module_path: ci.module_path(), - name: self.identifier.0.to_string(), - variants: self - .values - .body - .list - .iter() - .map::<Result<_>, _>(|v| { - Ok(VariantMetadata { - name: v.0.to_string(), - fields: vec![], - }) - }) - .collect::<Result<Vec<_>>>()?, - }, - is_flat: true, + non_exhaustive: attributes.contains_non_exhaustive_attr(), + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -61,11 +44,11 @@ impl APIConverter<EnumMetadata> for weedle::InterfaceDefinition<'_> { if self.inheritance.is_some() { bail!("interface inheritance is not supported for enum interfaces"); } - // We don't need to check `self.attributes` here; if calling code has dispatched - // to this impl then we already know there was an `[Enum]` attribute. + let attributes = EnumAttributes::try_from(self.attributes.as_ref())?; Ok(EnumMetadata { module_path: ci.module_path(), name: self.identifier.0.to_string(), + forced_flatness: Some(false), variants: self .members .body @@ -78,41 +61,15 @@ impl APIConverter<EnumMetadata> for weedle::InterfaceDefinition<'_> { ), }) .collect::<Result<Vec<_>>>()?, + discr_type: None, + non_exhaustive: attributes.contains_non_exhaustive_attr(), + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), // Enums declared using the `[Enum] interface` syntax might have variants with fields. //flat: false, }) } } -impl APIConverter<ErrorMetadata> for weedle::InterfaceDefinition<'_> { - fn convert(&self, ci: &mut InterfaceCollector) -> Result<ErrorMetadata> { - if self.inheritance.is_some() { - bail!("interface inheritance is not supported for enum interfaces"); - } - // We don't need to check `self.attributes` here; callers have already checked them - // to work out which version to dispatch to. - Ok(ErrorMetadata::Enum { - enum_: EnumMetadata { - module_path: ci.module_path(), - name: self.identifier.0.to_string(), - variants: self - .members - .body - .iter() - .map::<Result<VariantMetadata>, _>(|member| match member { - weedle::interface::InterfaceMember::Operation(t) => Ok(t.convert(ci)?), - _ => bail!( - "interface member type {:?} not supported in enum interface", - member - ), - }) - .collect::<Result<Vec<_>>>()?, - }, - is_flat: false, - }) - } -} - #[cfg(test)] mod test { use super::*; diff --git a/third_party/rust/uniffi_udl/src/converters/interface.rs b/third_party/rust/uniffi_udl/src/converters/interface.rs index 58e6a9c8a0..ef9bdd9540 100644 --- a/third_party/rust/uniffi_udl/src/converters/interface.rs +++ b/third_party/rust/uniffi_udl/src/converters/interface.rs @@ -4,7 +4,7 @@ use super::APIConverter; use crate::attributes::InterfaceAttributes; -use crate::InterfaceCollector; +use crate::{converters::convert_docstring, InterfaceCollector}; use anyhow::{bail, Result}; use std::collections::HashSet; use uniffi_meta::{ @@ -23,7 +23,7 @@ impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> { }; let object_name = self.identifier.0; - let object_impl = attributes.object_impl(); + let object_impl = attributes.object_impl()?; // Convert each member into a constructor or method, guarding against duplicate names. // They get added to the ci and aren't carried in ObjectMetadata. let mut member_names = HashSet::new(); @@ -70,6 +70,7 @@ impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> { throws: None, takes_self_by_arc: false, checksum: None, + docstring: None, }) }; // Trait methods are in the Metadata. @@ -130,6 +131,7 @@ impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> { module_path: ci.module_path(), name: object_name.to_string(), imp: object_impl, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } diff --git a/third_party/rust/uniffi_udl/src/converters/mod.rs b/third_party/rust/uniffi_udl/src/converters/mod.rs index 7a2d22ac42..195d9cc0b7 100644 --- a/third_party/rust/uniffi_udl/src/converters/mod.rs +++ b/third_party/rust/uniffi_udl/src/converters/mod.rs @@ -29,6 +29,11 @@ pub(crate) trait APIConverter<T> { fn convert(&self, ci: &mut InterfaceCollector) -> Result<T>; } +// Convert UDL docstring into metadata docstring +pub(crate) fn convert_docstring(docstring: &str) -> String { + textwrap::dedent(docstring) +} + /// Convert a list of weedle items into a list of `InterfaceCollector` items, /// by doing a direct item-by-item mapping. impl<U, T: APIConverter<U>> APIConverter<Vec<U>> for Vec<T> { @@ -72,6 +77,7 @@ impl APIConverter<VariantMetadata> for weedle::interface::OperationInterfaceMemb }; Ok(VariantMetadata { name, + discr: None, fields: self .args .body @@ -79,6 +85,7 @@ impl APIConverter<VariantMetadata> for weedle::interface::OperationInterfaceMemb .iter() .map(|arg| arg.convert(ci)) .collect::<Result<Vec<_>>>()?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -95,6 +102,7 @@ impl APIConverter<RecordMetadata> for weedle::DictionaryDefinition<'_> { module_path: ci.module_path(), name: self.identifier.0.to_string(), fields: self.members.body.convert(ci)?, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -113,6 +121,7 @@ impl APIConverter<FieldMetadata> for weedle::dictionary::DictionaryMember<'_> { name: self.identifier.0.to_string(), ty: type_, default, + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } @@ -150,6 +159,7 @@ impl APIConverter<CallbackInterfaceMetadata> for weedle::CallbackInterfaceDefini Ok(CallbackInterfaceMetadata { module_path: ci.module_path(), name: object_name.to_string(), + docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)), }) } } diff --git a/third_party/rust/uniffi_udl/src/finder.rs b/third_party/rust/uniffi_udl/src/finder.rs index 0c4c187dc0..259557ad07 100644 --- a/third_party/rust/uniffi_udl/src/finder.rs +++ b/third_party/rust/uniffi_udl/src/finder.rs @@ -22,8 +22,8 @@ use std::convert::TryFrom; use anyhow::{bail, Result}; use super::TypeCollector; -use crate::attributes::{InterfaceAttributes, TypedefAttributes}; -use uniffi_meta::Type; +use crate::attributes::{InterfaceAttributes, RustKind, TypedefAttributes}; +use uniffi_meta::{ObjectImpl, Type}; /// Trait to help with an early "type discovery" phase when processing the UDL. /// @@ -75,7 +75,7 @@ impl TypeFinder for weedle::InterfaceDefinition<'_> { Type::Object { name, module_path: types.module_path(), - imp: attrs.object_impl(), + imp: attrs.object_impl()?, }, ) } @@ -111,7 +111,6 @@ impl TypeFinder for weedle::EnumDefinition<'_> { impl TypeFinder for weedle::TypedefDefinition<'_> { fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> { - let name = self.identifier.0; let attrs = TypedefAttributes::try_from(self.attributes.as_ref())?; // If we wanted simple `typedef`s, it would be as easy as: // > let t = types.resolve_type_expression(&self.type_)?; @@ -122,29 +121,52 @@ impl TypeFinder for weedle::TypedefDefinition<'_> { // `FfiConverter` implementation. let builtin = types.resolve_type_expression(&self.type_)?; types.add_type_definition( - name, + self.identifier.0, Type::Custom { module_path: types.module_path(), - name: name.to_string(), + name: self.identifier.0.to_string(), builtin: builtin.into(), }, ) } else { - let kind = attrs.external_kind().expect("External missing"); - let tagged = attrs.external_tagged().expect("External missing"); + let module_path = types.module_path(); + let name = self.identifier.0.to_string(); + let ty = match attrs.rust_kind() { + Some(RustKind::Object) => Type::Object { + module_path, + name, + imp: ObjectImpl::Struct, + }, + Some(RustKind::Trait) => Type::Object { + module_path, + name, + imp: ObjectImpl::Trait, + }, + Some(RustKind::CallbackTrait) => Type::Object { + module_path, + name, + imp: ObjectImpl::CallbackTrait, + }, + Some(RustKind::Record) => Type::Record { module_path, name }, + Some(RustKind::Enum) => Type::Enum { module_path, name }, + Some(RustKind::CallbackInterface) => Type::CallbackInterface { module_path, name }, + // must be external + None => { + let kind = attrs.external_kind().expect("External missing kind"); + let tagged = attrs.external_tagged().expect("External missing tagged"); + Type::External { + name, + namespace: "".to_string(), // we don't know this yet + module_path: attrs.get_crate_name(), + kind, + tagged, + } + } + }; // A crate which can supply an `FfiConverter`. // We don't reference `self._type`, so ideally we could insist on it being // the literal 'extern' but that's tricky - types.add_type_definition( - name, - Type::External { - name: name.to_string(), - namespace: "".to_string(), // we don't know this yet - module_path: attrs.get_crate_name(), - kind, - tagged, - }, - ) + types.add_type_definition(self.identifier.0, ty) } } } @@ -152,7 +174,7 @@ impl TypeFinder for weedle::TypedefDefinition<'_> { impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> { fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> { if self.attributes.is_some() { - bail!("no typedef attributes are currently supported"); + bail!("no callback interface attributes are currently supported"); } let name = self.identifier.0.to_string(); types.add_type_definition( diff --git a/third_party/rust/uniffi_udl/src/lib.rs b/third_party/rust/uniffi_udl/src/lib.rs index 5e6e72a7f7..a9dad5d6c5 100644 --- a/third_party/rust/uniffi_udl/src/lib.rs +++ b/third_party/rust/uniffi_udl/src/lib.rs @@ -6,7 +6,7 @@ //! //! This library is dedicated to parsing a string in a webidl syntax, as described by //! weedle and with our own custom take on the attributes etc, pushing the boundaries -//! of that syntax to describe a uniffi `MetatadataGroup`. +//! of that syntax to describe a uniffi `MetadataGroup`. //! //! The output of this module is consumed by uniffi_bindgen to generate stuff. diff --git a/third_party/rust/uniffi_udl/src/literal.rs b/third_party/rust/uniffi_udl/src/literal.rs index 78f2544254..5fbf022644 100644 --- a/third_party/rust/uniffi_udl/src/literal.rs +++ b/third_party/rust/uniffi_udl/src/literal.rs @@ -84,8 +84,10 @@ pub(super) fn convert_default_value( (weedle::literal::DefaultValue::String(s), Type::Enum { .. }) => { Literal::Enum(s.0.to_string(), type_.clone()) } - (weedle::literal::DefaultValue::Null(_), Type::Optional { .. }) => Literal::Null, - (_, Type::Optional { inner_type, .. }) => convert_default_value(default_value, inner_type)?, + (weedle::literal::DefaultValue::Null(_), Type::Optional { .. }) => Literal::None, + (_, Type::Optional { inner_type, .. }) => Literal::Some { + inner: Box::new(convert_default_value(default_value, inner_type)?), + }, // We'll ensure the type safety in the convert_* number methods. (weedle::literal::DefaultValue::Integer(i), _) => convert_integer(i, type_)?, @@ -144,7 +146,7 @@ mod test { inner_type: Box::new(Type::String) } )?, - Literal::Null + Literal::None )); Ok(()) } diff --git a/third_party/rust/uniffi_udl/src/resolver.rs b/third_party/rust/uniffi_udl/src/resolver.rs index 14a7a4c6f1..ea98cd7a99 100644 --- a/third_party/rust/uniffi_udl/src/resolver.rs +++ b/third_party/rust/uniffi_udl/src/resolver.rs @@ -209,7 +209,6 @@ pub(crate) fn resolve_builtin_type(name: &str) -> Option<Type> { "f64" => Some(Type::Float64), "timestamp" => Some(Type::Timestamp), "duration" => Some(Type::Duration), - "ForeignExecutor" => Some(Type::ForeignExecutor), _ => None, } } |