Skip to content

Commit

Permalink
Add example of using swift-ui from Rust
Browse files Browse the repository at this point in the history
  • Loading branch information
chinedufn committed Nov 28, 2021
1 parent 0ca31d4 commit 930866e
Show file tree
Hide file tree
Showing 37 changed files with 830 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
220432A7274C953E00BAE645 /* PointerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432A6274C953E00BAE645 /* PointerTests.swift */; };
220432A9274D31DC00BAE645 /* Pointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432A8274D31DC00BAE645 /* Pointer.swift */; };
220432AF274E7BF800BAE645 /* SharedTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432AE274E7BF800BAE645 /* SharedTypes.swift */; };
220432EA2753092C00BAE645 /* RustFnReturnOpaqueSwiftType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432E92753092C00BAE645 /* RustFnReturnOpaqueSwiftType.swift */; };
220432EC27530AFC00BAE645 /* RustFnReturnOpaqueSwiftTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432EB27530AFC00BAE645 /* RustFnReturnOpaqueSwiftTypeTests.swift */; };
220432EA2753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432E92753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift */; };
220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */; };
228FE5D52740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */; };
228FE5D72740DB6A00805D9E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE5D62740DB6A00805D9E /* ContentView.swift */; };
228FE5D92740DB6D00805D9E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 228FE5D82740DB6D00805D9E /* Assets.xcassets */; };
Expand All @@ -29,6 +29,8 @@
228FE64A274919C600805D9E /* swift-integration-tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE648274919C500805D9E /* swift-integration-tests.swift */; };
228FE64E2749C3D700805D9E /* Boolean.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE64D2749C3D700805D9E /* Boolean.swift */; };
228FE6502749C43100805D9E /* BooleanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE64F2749C43100805D9E /* BooleanTests.swift */; };
22FD1C542753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD1C532753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift */; };
22FD1C562753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -48,8 +50,8 @@
220432A6274C953E00BAE645 /* PointerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointerTests.swift; sourceTree = "<group>"; };
220432A8274D31DC00BAE645 /* Pointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pointer.swift; sourceTree = "<group>"; };
220432AE274E7BF800BAE645 /* SharedTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedTypes.swift; sourceTree = "<group>"; };
220432E92753092C00BAE645 /* RustFnReturnOpaqueSwiftType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnReturnOpaqueSwiftType.swift; sourceTree = "<group>"; };
220432EB27530AFC00BAE645 /* RustFnReturnOpaqueSwiftTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnReturnOpaqueSwiftTypeTests.swift; sourceTree = "<group>"; };
220432E92753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnUsesOpaqueSwiftType.swift; sourceTree = "<group>"; };
220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFnUsesOpaqueSwiftTypeTests.swift; sourceTree = "<group>"; };
228FE5D12740DB6A00805D9E /* SwiftRustIntegrationTestRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftRustIntegrationTestRunner.app; sourceTree = BUILT_PRODUCTS_DIR; };
228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftRustIntegrationTestRunnerApp.swift; sourceTree = "<group>"; };
228FE5D62740DB6A00805D9E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
Expand All @@ -70,6 +72,8 @@
228FE649274919C600805D9E /* swift-integration-tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "swift-integration-tests.h"; path = "swift-integration-tests/swift-integration-tests.h"; sourceTree = "<group>"; };
228FE64D2749C3D700805D9E /* Boolean.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Boolean.swift; sourceTree = "<group>"; };
228FE64F2749C43100805D9E /* BooleanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BooleanTests.swift; sourceTree = "<group>"; };
22FD1C532753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFnUsesOpaqueRustType.swift; sourceTree = "<group>"; };
22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftFnUsesOpaqueRustTypeTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -125,7 +129,8 @@
228FE5DA2740DB6D00805D9E /* Preview Content */,
22043296274B0AB000BAE645 /* Option.swift */,
220432A8274D31DC00BAE645 /* Pointer.swift */,
220432E92753092C00BAE645 /* RustFnReturnOpaqueSwiftType.swift */,
220432E92753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift */,
22FD1C532753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift */,
);
path = SwiftRustIntegrationTestRunner;
sourceTree = "<group>";
Expand All @@ -149,7 +154,8 @@
22043294274ADA7A00BAE645 /* OptionTests.swift */,
220432A6274C953E00BAE645 /* PointerTests.swift */,
220432AE274E7BF800BAE645 /* SharedTypes.swift */,
220432EB27530AFC00BAE645 /* RustFnReturnOpaqueSwiftTypeTests.swift */,
220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */,
22FD1C552753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift */,
);
path = SwiftRustIntegrationTestRunnerTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -304,7 +310,8 @@
buildActionMask = 2147483647;
files = (
22043297274B0AB000BAE645 /* Option.swift in Sources */,
220432EA2753092C00BAE645 /* RustFnReturnOpaqueSwiftType.swift in Sources */,
220432EA2753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift in Sources */,
22FD1C542753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift in Sources */,
220432A9274D31DC00BAE645 /* Pointer.swift in Sources */,
228FE5D72740DB6A00805D9E /* ContentView.swift in Sources */,
228FE64E2749C3D700805D9E /* Boolean.swift in Sources */,
Expand All @@ -322,11 +329,12 @@
22043293274A8FDF00BAE645 /* VecTests.swift in Sources */,
220432A7274C953E00BAE645 /* PointerTests.swift in Sources */,
220432AF274E7BF800BAE645 /* SharedTypes.swift in Sources */,
220432EC27530AFC00BAE645 /* RustFnReturnOpaqueSwiftTypeTests.swift in Sources */,
220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */,
228FE6502749C43100805D9E /* BooleanTests.swift in Sources */,
22043295274ADA7A00BAE645 /* OptionTests.swift in Sources */,
228FE5E72740DB6D00805D9E /* StringTests.swift in Sources */,
228FE61227428A8D00805D9E /* OpaqueSwiftStructTests.swift in Sources */,
22FD1C562753CB3F00F64281 /* SwiftFnUsesOpaqueRustTypeTests.swift in Sources */,
228FE61027416C0300805D9E /* OpaqueRustStructTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// SwiftFnUsesOpaqueRustType.swift
// SwiftRustIntegrationTestRunner
//
// Created by Frankie Nwafili on 11/28/21.
//

import Foundation

func increment_some_owned_opaque_rust_type(arg: SomeRustType, amount: UInt32) {
arg.increment_counter(amount)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// SwiftFnUsesOpaqueRustTypeTests.swift
// SwiftRustIntegrationTestRunnerTests
//
// Created by Frankie Nwafili on 11/28/21.
//

import XCTest
@testable import SwiftRustIntegrationTestRunner


class SwiftFnUsesOpaqueRustTypeTests: XCTestCase {

override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}

func testRustFnCallsWithFnWithOwnedOpaqueArg() throws {
test_call_swift_fn_with_owned_opaque_rust_arg()
}
}
22 changes: 22 additions & 0 deletions book/src/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# swift-bridge

`swift-bridge` generates bindings for calling Rust from Swift and vice versa.

# Work In Progress

The `swift-bridge` book is a work-in-progress with many chapter either sparse or empty.

We've love your help in improving it!

Here's how you can contribute to the book even if you don't know much about `swift-bridge`.

1. You came to the book in order to figure out how to do something or get an answer to a question.

2. The book did not have a section that answered your question or pointed you in the right direction.

3. [Open a pull request][pulls] where you fill out a new or existing section with questions that you have.

4. A maintainer will answer your questions in the pull request comments.

5. Using your newfound understanding, replace your questions with text that answers them.

6. Pull request merged!

[pulls]: https://github.com/chinedufn/swift-bridge/pulls
3 changes: 3 additions & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
- [Built In Types](./built-in/README.md)
- [Option<T> <---> Optional<T>](./built-in/option/README.md)
- [Vec<T> <---> RustVec<T>](./built-in/vec/README.md)

- [Internal Design](./internal-design/README.md)
- [Code Generation](./internal-design/codegen/README.md)
3 changes: 3 additions & 0 deletions book/src/internal-design/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Design of `swift-bridge`

This chapter explores how `swift-bridge` works internally.
9 changes: 9 additions & 0 deletions book/src/internal-design/codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Code Generation

- Talk about Rust token stream generation

- Talk about Swift codegen

- Talk about C header codegen

- Talk about how we test codegen
4 changes: 4 additions & 0 deletions crates/swift-bridge-ir/src/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
mod generate_c_header;
mod generate_swift;
mod generate_rust_tokens;

#[cfg(test)]
mod codegen_tests;
195 changes: 195 additions & 0 deletions crates/swift-bridge-ir/src/codegen/codegen_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//! Tests for codegen for different scenarios.
//!
//! Tests are grouped into modules, each of which takes a set of tokens and then tests that the
//! generated Rust, Swift and C code matches what we expect.
//!
//! This entire module is conditionally compiled with `#[cfg(test)]`, so there is no need to
//! conditionally compile it's submodules.
//!
//! We previously kept out Rust, Swift and C codegen tests in separate files, but then moved to
//! this approach to make it easier to reason about our codegen.
//!
//! There are a bunch of tests in generate_rust_tokens.rs generate_swift.rs and
//! generate_c_header.rs that were written before this module was created. They should be
//! re-organized into this module over time.
#![cfg(test)]

use crate::test_utils::{
assert_generated_contains_expected, assert_generated_equals_expected, assert_tokens_contain,
assert_tokens_eq, parse_ok,
};
use proc_macro2::TokenStream;
use quote::quote;
use quote::ToTokens;

/// Test code generation for freestanding Swift function that takes an opaque Rust type argument.
mod extern_swift_freestanding_fn_with_owned_opaque_rust_type_arg {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod foo {
extern "Rust" {
type MyType;
}

extern "Swift" {
fn some_function (arg: MyType);
}
}
}
}

fn expected_rust_tokens() -> TokenStream {
quote! {
pub fn some_function (arg: super::MyType) {
unsafe { __swift_bridge__some_function( Box::into_raw(Box::new(arg)) ) }
}

extern "C" {
#[link_name = "__swift_bridge__$some_function"]
fn __swift_bridge__some_function (arg: *mut super::MyType);
}
}
}

const EXPECTED_SWIFT: &'static str = r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ arg: UnsafeMutableRawPointer) {
some_function(arg: MyType(ptr: arg, isOwned: true))
}
"#;

const EXPECTED_C_HEADER: &'static str = r#"
typedef struct MyType MyType;
void __swift_bridge__$MyType$_free(void* self);
"#;

#[test]
fn extern_swift_freestanding_fn_with_owned_opaque_rust_type_arg() {
CodegenTest {
bridge_module_tokens: bridge_module_tokens(),
expected_rust_tokens: ExpectedRustTokens::Contains(expected_rust_tokens()),
expected_swift_code: ExpectedSwiftCode::Contains(EXPECTED_SWIFT),
expected_c_header: EXPECTED_C_HEADER,
}
.test();
}
}

/// Test code generation for freestanding Swift function that takes an opaque Swift type argument.
mod extern_swift_freestanding_fn_with_owned_opaque_swift_type_arg {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod foo {
extern "Swift" {
type MyType;
fn some_function (arg: MyType);
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
pub fn some_function (arg: MyType) {
unsafe { __swift_bridge__some_function (arg) }
}

#[repr(C)]
pub struct MyType(*mut std::ffi::c_void);

impl Drop for MyType {
fn drop (&mut self) {
unsafe { __swift_bridge__MyType__free(self.0) }
}
}

extern "C" {
#[link_name = "__swift_bridge__$some_function"]
fn __swift_bridge__some_function (arg: MyType);

#[link_name = "__swift_bridge__$MyType$_free"]
fn __swift_bridge__MyType__free (this: *mut std::ffi::c_void);
}
})
}

const EXPECTED_SWIFT_CODE: ExpectedSwiftCode = ExpectedSwiftCode::Contains(
r#"
@_cdecl("__swift_bridge__$some_function")
func __swift_bridge__some_function (_ arg: __private__PointerToSwiftType) {
some_function(arg: Unmanaged<MyType>.fromOpaque(arg.ptr).takeRetainedValue())
}
"#,
);

const EXPECTED_C_HEADER: &'static str = r#""#;

#[test]
fn extern_swift_freestanding_fn_with_owned_opaque_swift_type_arg() {
CodegenTest {
bridge_module_tokens: bridge_module_tokens(),
expected_rust_tokens: expected_rust_tokens(),
expected_swift_code: EXPECTED_SWIFT_CODE,
expected_c_header: EXPECTED_C_HEADER,
}
.test();
}
}

struct CodegenTest {
bridge_module_tokens: TokenStream,
// Gets turned into a Vec<String> and compared to a Vec<String> of the generated Rust tokens.
expected_rust_tokens: ExpectedRustTokens,
// Gets trimmed and compared to the generated Swift code.
expected_swift_code: ExpectedSwiftCode,
// Gets trimmed and compared to the generated C header.
expected_c_header: &'static str,
}

enum ExpectedRustTokens {
/// The generated Rust token stream matches the provided stream.
Exact(TokenStream),
/// The generated Rust tokens stream contains the provided stream.
Contains(TokenStream),
}

enum ExpectedSwiftCode {
/// Assert that after we trim our
ExactAfterTrim(&'static str),
Contains(&'static str),
}

impl CodegenTest {
fn test(self) {
let module = parse_ok(self.bridge_module_tokens);
let generated_tokens = module.to_token_stream();

match self.expected_rust_tokens {
ExpectedRustTokens::Exact(expected_tokens) => {
assert_tokens_eq(&generated_tokens, &expected_tokens);
}
ExpectedRustTokens::Contains(expected_contained_tokens) => {
assert_tokens_contain(&generated_tokens, &expected_contained_tokens);
}
};

match self.expected_swift_code {
ExpectedSwiftCode::ExactAfterTrim(expected_swift) => {
assert_generated_equals_expected(&module.generate_swift(), expected_swift);
}
ExpectedSwiftCode::Contains(expected_contained_swift) => {
assert_generated_contains_expected(
&module.generate_swift(),
expected_contained_swift,
);
}
};

assert_generated_equals_expected(&module.generate_c_header_inner(), self.expected_c_header);
}
}
2 changes: 1 addition & 1 deletion crates/swift-bridge-ir/src/codegen/generate_c_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl SwiftBridgeModule {
)
}

fn generate_c_header_inner(&self) -> String {
pub(crate) fn generate_c_header_inner(&self) -> String {
let mut header = "".to_string();

let mut bookkeeping = Bookkeeping {
Expand Down
Loading

0 comments on commit 930866e

Please sign in to comment.