Skip to content

Commit

Permalink
Cleanup the CodeOracle by removing CodeTypeDispatch entirely (#1588)
Browse files Browse the repository at this point in the history
This fixes a source of significant confusion in the code-oracle support.

* Every `CodeType` method takes a `CodeOracle` param - which is odd
  because the `CodeType` came from the oracle - it doesn't need the
  oracle to work.

* There's a blanket implementation of `CodeType` made available for
  `Type` - so not only do we now have a `CodeType` that didn't come
  from an Oracle, so clearly can't know the language semantics the
  Oracle abstracts away, there's now *two* implementations of `CodeType`
  for each type, with each behaving differently.

This cleans all that up:

* CodeType is only supplied by Oracles - there's no blanket
  implementation for Type.

* Therefore `CodeType` no longer takes Oracle's as args.

* `CodeTypeDispatch` has been replaced by an `AsType` trait, which
  is implemented by Type itself and all the structs used by the codegen.
  • Loading branch information
mhammond authored Jun 8, 2023
1 parent e002626 commit 07dcf3f
Show file tree
Hide file tree
Showing 59 changed files with 468 additions and 514 deletions.
3 changes: 1 addition & 2 deletions uniffi_bindgen/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@ pub use declarations::CodeDeclaration;
pub use oracle::CodeOracle;
pub use types::CodeType;

pub type TypeIdentifier = crate::interface::Type;
pub type Literal = crate::interface::Literal;
pub use crate::interface::{Literal, Type};
18 changes: 6 additions & 12 deletions uniffi_bindgen/src/backend/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@
* 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, TypeIdentifier};
use crate::interface::FfiType;

/// An object to look up a foreign language code specific renderer for a given type used.
/// Every `Type` referred to in the `ComponentInterface` should map to a corresponding
/// `CodeType`.
///
/// The mapping may be opaque, but the oracle always knows the answer.
///
/// In adddition, the oracle knows how to render identifiers (function names,
/// class names, variable names etc).
use super::CodeType;
use crate::interface::{FfiType, Type};

/// An object to supply a foreign language specific CodeType for a given type. It also
/// supplys the specific rendering of a given identifier when used in a specific context.
pub trait CodeOracle {
fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType>;
fn find(&self, type_: &Type) -> Box<dyn CodeType>;

/// Get the idiomatic rendering of a class name (for enums, records, errors, etc).
fn class_name(&self, nm: &str) -> String;
Expand Down
168 changes: 25 additions & 143 deletions uniffi_bindgen/src/backend/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,39 @@
//! (the FfiType), and foreign language expressions that call into the machinery. This helper code
//! can be provided by a template file.
//!
//! A `CodeDeclaration` is needed for each type that is declared in the UDL file. This has access to
//! the [crate::interface::ComponentInterface], which is the closest thing to an Intermediate Representation.
//!
//! `CodeDeclaration`s provide the target language's version of the UDL type, including forwarding method calls
//! into Rust. It is likely if you're implementing a `CodeDeclaration` for this purpose, it will also need to cross
//! the FFI, and you'll also need a `CodeType`.
//!
//! `CodeDeclaration`s can also be used to conditionally include code: e.g. only include the CallbackInterfaceRuntime
//! if the user has used at least one callback interface.
//!
//! Each backend has a wrapper template for each file it needs to generate. This should collect the `CodeDeclaration`s that
//! the backend and `ComponentInterface` between them specify and use them to stitch together a file in the target language.
//!
//! The `CodeOracle` provides methods to map the `Type` values found in the `ComponentInterface` to the `CodeType`s specified
//! by the backend. It also provides methods for transforming identifiers into the coding standard for the target language.
//!
//! There's also a CodeTypeDispatch trait, implemented for every type, which allows a CodeType to be created
//! by the specified `CodeOracle`. This means backends are able to provide a custom CodeType for each type
//! via that backend's CodeOracle.
//!
//! Each backend will have its own `filter` module, which is used by the askama templates used in all `CodeType`s and `CodeDeclaration`s.
//! This filter provides methods to generate expressions and identifiers in the target language. These are all forwarded to the oracle.

use super::{CodeOracle, Literal};
use std::fmt::Debug;

use crate::interface::*;

/// A Trait to emit foreign language code to handle referenced types.
/// A type which is specified in the UDL (i.e. a member of the component interface)
/// will have a `CodeDeclaration` as well, but for types used e.g. primitive types, Strings, etc
/// only a `CodeType` is needed.
pub trait CodeType {
/// A Trait to help render types in a language specific format.
pub trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
/// method signatures and property declarations.
fn type_label(&self, oracle: &dyn CodeOracle) -> String;
fn type_label(&self) -> String;

/// A representation of this type label that can be used as part of another
/// identifier. e.g. `read_foo()`, or `FooInternals`.
///
/// This is especially useful when creating specialized objects or methods to deal
/// with this type only.
fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
self.type_label(oracle)
fn canonical_name(&self) -> String {
self.type_label()
}

/// A representation of the given literal for this type.
/// N.B. `Literal` is aliased from `interface::Literal`, so may not be whole suited to this task.
fn literal(&self, oracle: &dyn CodeOracle, _literal: &Literal) -> String {
unimplemented!("Unimplemented for {}", self.type_label(oracle))
fn literal(&self, _literal: &Literal) -> String {
unimplemented!("Unimplemented for {}", self.type_label())
}

/// Name of the FfiConverter
Expand All @@ -65,145 +54,38 @@ pub trait CodeType {
/// This is the newer way of handling these methods and replaces the lower, write, lift, and
/// read CodeType methods. Currently only used by Kotlin, but the plan is to move other
/// backends to using this.
fn ffi_converter_name(&self, oracle: &dyn CodeOracle) -> String {
oracle.class_name(&format!("FfiConverter{}", self.canonical_name(oracle)))
fn ffi_converter_name(&self) -> String {
format!("FfiConverter{}", self.canonical_name())
}

/// An expression for lowering a value into something we can pass over the FFI.
fn lower(&self, oracle: &dyn CodeOracle) -> String {
format!("{}.lower", self.ffi_converter_name(oracle))
fn lower(&self) -> String {
format!("{}.lower", self.ffi_converter_name())
}

/// An expression for writing a value into a byte buffer.
fn write(&self, oracle: &dyn CodeOracle) -> String {
format!("{}.write", self.ffi_converter_name(oracle))
fn write(&self) -> String {
format!("{}.write", self.ffi_converter_name())
}

/// An expression for lifting a value from something we received over the FFI.
fn lift(&self, oracle: &dyn CodeOracle) -> String {
format!("{}.lift", self.ffi_converter_name(oracle))
fn lift(&self) -> String {
format!("{}.lift", self.ffi_converter_name())
}

/// An expression for reading a value from a byte buffer.
fn read(&self, oracle: &dyn CodeOracle) -> String {
format!("{}.read", self.ffi_converter_name(oracle))
fn read(&self) -> String {
format!("{}.read", self.ffi_converter_name())
}

/// A list of imports that are needed if this type is in use.
/// Classes are imported exactly once.
fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
fn imports(&self) -> Option<Vec<String>> {
None
}

/// Function to run at startup
fn initialization_fn(&self, _oracle: &dyn CodeOracle) -> Option<String> {
fn initialization_fn(&self) -> Option<String> {
None
}
}

/// This trait is used to implement `CodeType` for `Type` and type-like structs (`Record`, `Enum`, `Field`,
/// etc). We forward all method calls to a `Box<dyn CodeType>`, which we get by calling
/// `CodeOracle.find()`.
pub trait CodeTypeDispatch {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType>;
}

impl CodeTypeDispatch for Type {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(self)
}
}

impl CodeTypeDispatch for Record {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(&self.type_())
}
}

impl CodeTypeDispatch for Enum {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(&self.type_())
}
}

impl CodeTypeDispatch for Error {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(&self.type_())
}
}

impl CodeTypeDispatch for Object {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(&self.type_())
}
}

impl CodeTypeDispatch for CallbackInterface {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(&self.type_())
}
}

impl CodeTypeDispatch for Field {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(self.type_())
}
}

impl CodeTypeDispatch for Argument {
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
oracle.find(self.type_())
}
}

// Needed to handle &&Type and &&&Type values, which we sometimes end up with in the template code
impl<T, C> CodeTypeDispatch for T
where
T: std::ops::Deref<Target = C>,
C: CodeTypeDispatch,
{
fn code_type_impl(&self, oracle: &dyn CodeOracle) -> Box<dyn CodeType> {
self.deref().code_type_impl(oracle)
}
}

impl<T: CodeTypeDispatch> CodeType for T {
// The above code implements `CodeTypeDispatch` for `Type` and type-like structs (`Record`,
// `Enum`, `Field`, etc). Now we can leverage that to implement `CodeType` for all of them.
// This allows for simpler template code (`field|lower` instead of `field.type_()|lower`)
fn type_label(&self, oracle: &dyn CodeOracle) -> String {
self.code_type_impl(oracle).type_label(oracle)
}

fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
self.code_type_impl(oracle).canonical_name(oracle)
}

fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
self.code_type_impl(oracle).literal(oracle, literal)
}

fn lower(&self, oracle: &dyn CodeOracle) -> String {
self.code_type_impl(oracle).lower(oracle)
}

fn write(&self, oracle: &dyn CodeOracle) -> String {
self.code_type_impl(oracle).write(oracle)
}

fn lift(&self, oracle: &dyn CodeOracle) -> String {
self.code_type_impl(oracle).lift(oracle)
}

fn read(&self, oracle: &dyn CodeOracle) -> String {
self.code_type_impl(oracle).read(oracle)
}

fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
self.code_type_impl(oracle).imports(oracle)
}

fn initialization_fn(&self, oracle: &dyn CodeOracle) -> Option<String> {
self.code_type_impl(oracle).initialization_fn(oracle)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use crate::backend::{CodeOracle, CodeType, Literal};

#[derive(Debug)]
pub struct CallbackInterfaceCodeType {
id: String,
}
Expand All @@ -15,19 +16,19 @@ impl CallbackInterfaceCodeType {
}

impl CodeType for CallbackInterfaceCodeType {
fn type_label(&self, oracle: &dyn CodeOracle) -> String {
oracle.class_name(&self.id)
fn type_label(&self) -> String {
super::KotlinCodeOracle.class_name(&self.id)
}

fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
fn canonical_name(&self) -> String {
format!("Type{}", self.id)
}

fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
fn literal(&self, _literal: &Literal) -> String {
unreachable!();
}

fn initialization_fn(&self, oracle: &dyn CodeOracle) -> Option<String> {
Some(format!("{}.register", self.ffi_converter_name(oracle)))
fn initialization_fn(&self) -> Option<String> {
Some(format!("{}.register", self.ffi_converter_name()))
}
}
Loading

0 comments on commit 07dcf3f

Please sign in to comment.