Skip to content

Commit

Permalink
WIP actually implement [TaggedUnion].
Browse files Browse the repository at this point in the history
  • Loading branch information
rfk committed Feb 9, 2021
1 parent 281c108 commit 45554da
Show file tree
Hide file tree
Showing 17 changed files with 539 additions and 109 deletions.
10 changes: 6 additions & 4 deletions examples/rondpoint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ pub enum Enumeration {

#[derive(Debug, Clone)]
pub enum EnumerationAvecDonnees {
Zero,
Un { premier: u32 },
Deux { premier: u32, second: String },
Zero,
Un { premier: u32 },
Deux { premier: u32, second: String },
}

#[allow(non_camel_case_types)]
Expand All @@ -61,7 +61,9 @@ fn copie_enumerations(e: Vec<Enumeration>) -> Vec<Enumeration> {
e
}

fn copie_carte(e: HashMap<String, EnumerationAvecDonnees>) -> HashMap<String, EnumerationAvecDonnees> {
fn copie_carte(
e: HashMap<String, EnumerationAvecDonnees>,
) -> HashMap<String, EnumerationAvecDonnees> {
e
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ namespace dom {
namespace {{ context.detail_name() }} {

{% for e in ci.iter_enum_definitions() %}
{% if e.has_associated_data() %}
MOZ_STATIC_ASSERT(false, "Sorry the gecko-js backend does not yet support enums with associated data");
{% else %}
template <>
struct ViaFfi<{{ e.name()|class_name_cpp(context) }}, uint32_t> {
[[nodiscard]] static bool Lift(const uint32_t& aLowered, {{ e.name()|class_name_cpp(context) }}& aLifted) {
switch (aLowered) {
{% for variant in e.variants() -%}
case {{ loop.index }}:
aLifted = {{ e.name()|class_name_cpp(context) }}::{{ variant|enum_variant_cpp }};
aLifted = {{ e.name()|class_name_cpp(context) }}::{{ variant.name()|enum_variant_cpp }};
break;
{% endfor -%}
default:
Expand All @@ -48,7 +51,7 @@ struct ViaFfi<{{ e.name()|class_name_cpp(context) }}, uint32_t> {
[[nodiscard]] static uint32_t Lower(const {{ e.name()|class_name_cpp(context) }}& aLifted) {
switch (aLifted) {
{% for variant in e.variants() -%}
case {{ e.name()|class_name_cpp(context) }}::{{ variant|enum_variant_cpp }}:
case {{ e.name()|class_name_cpp(context) }}::{{ variant.name()|enum_variant_cpp }}:
return {{ loop.index }};
{% endfor -%}
default:
Expand All @@ -69,6 +72,7 @@ struct Serializable<{{ e.name()|class_name_cpp(context) }}> {
aWriter.WriteUInt32(ViaFfi<{{ e.name()|class_name_cpp(context) }}, uint32_t>::Lower(aValue));
}
};
{% endif %}
{% endfor %}

{% for rec in ci.iter_record_definitions() -%}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ dictionary {{ rec.name()|class_name_webidl(context) }} {
{% endfor %}

{%- for e in ci.iter_enum_definitions() %}
{% if e.has_associated_data() %}
MOZ_STATIC_ASSERT(false, "Sorry the gecko-js backend does not yet support enums with associated data");
{% else %}
enum {{ e.name()|class_name_webidl(context) }} {
{% for variant in e.variants() %}
"{{ variant|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %}
"{{ variant.name()|enum_variant_webidl }}"{%- if !loop.last %}, {% endif %}
{% endfor %}
};
{% endif %}
{% endfor %}

{%- let functions = ci.iter_function_definitions() %}
Expand Down
67 changes: 60 additions & 7 deletions uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,72 @@
{#
// Kotlin's `enum class` constuct doesn't support variants with associated data,
// but is a little nicer for consumers than its `sealed class` enum pattern.
// So, we switch here, using `enum class` for enums with no associated data
// and `sealed class` for the general case.
#}

{% if e.has_associated_data() %}

sealed class {{ e.name()|class_name_kt }} {
{% for variant in e.variants() %}
class {{ variant.name()|class_name_kt }}() : {{ e.name()|class_name_kt }} {
override fun write(buf: RustBufferBuilder) {
buf.putInt({{ loop.index }})
// TODO: serialize fields here, if any
}
}
{% endfor %}

companion object {
internal fun lift(rbuf: RustBuffer.ByValue): {{ e.name()|class_name_kt }} {
return liftFromRustBuffer(rbuf) { buf -> {{ e.name()|class_name_kt }}.read(buf) }
}

internal fun read(buf: ByteBuffer): {{ e.name()|class_name_kt }} {
return when(buf.getInt()) {
{%- for variant in e.variants() %}
{{ loop.index }} -> {{ e.name()|class_name_kt }}.{{ variant.name()|class_name_kt }}(/* TODO: read fields here, if any */)
{%- endfor %}
else -> throw RuntimeException("invalid enum value, something is very wrong!!")
}
}
}

internal fun lower(): RustBuffer.ByValue {
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
}

internal fun write(buf: RustBufferBuilder) {
throw RuntimeException("enum variant should have overridden `write` method, something is very wrong!!")
}
}

{% else %}

enum class {{ e.name()|class_name_kt }} {
{% for variant in e.variants() %}
{{ variant|enum_variant_kt }}{% if loop.last %};{% else %},{% endif %}
{{ variant.name()|enum_variant_kt }}{% if loop.last %};{% else %},{% endif %}
{% endfor %}

companion object {
internal fun lift(n: Int) =
try { values()[n - 1] }
internal fun lift(rbuf: RustBuffer.ByValue): {{ e.name()|class_name_kt }} {
return liftFromRustBuffer(rbuf) { buf -> {{ e.name()|class_name_kt }}.read(buf) }
}

internal fun read(buf: ByteBuffer) =
try { values()[buf.getInt() - 1] }
catch (e: IndexOutOfBoundsException) {
throw RuntimeException("invalid enum value, something is very wrong!!", e)
}

internal fun read(buf: ByteBuffer) = lift(buf.getInt())
}

internal fun lower() = this.ordinal + 1
internal fun lower(): RustBuffer.ByValue {
return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)})
}

internal fun write(buf: RustBufferBuilder) = buf.putInt(this.lower())
internal fun write(buf: RustBufferBuilder) {
buf.putInt(this.ordinal + 1)
}
}

{% endif %}
6 changes: 5 additions & 1 deletion uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{% if e.has_associated_data() %}
assert False, "Sorry, the python backend doesn't yet support enums with associated data"
{% else %}
class {{ e.name()|class_name_py }}(enum.Enum):
{% for variant in e.variants() -%}
{{ variant|enum_name_py }} = {{ loop.index }}
{{ variant.name()|enum_name_py }} = {{ loop.index }}
{% endfor %}
{% endif %}
22 changes: 9 additions & 13 deletions uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
public enum {{ e.name()|class_name_swift }}: ViaFfi {
public enum {{ e.name()|class_name_swift }}: ViaFfiUsingByteBuffer, ViaFfi {
{% for variant in e.variants() %}
case {{ variant|enum_variant_swift }}
case {{ variant.name()|enum_variant_swift }}{% if variant.fields().len() > 0 %}(
{% for field in variant.fields() %}
{{ field.name()|var_name_swift }}: {{ field.type_()|type_swift }}{%- if loop.last -%}{%- else -%},{%- endif -%}
{% endfor %}
){% endif %}
{% endfor %}

static func read(from buf: Reader) throws -> {{ e.name()|class_name_swift }} {
return try {{ e.name()|class_name_swift }}.lift(UInt32.read(from: buf))
}

static func lift(_ number: UInt32) throws -> {{ e.name()|class_name_swift }} {
switch number {
switch buf.getInt() {
{% for variant in e.variants() %}
case {{ loop.index }}: return .{{ variant|enum_variant_swift }}
case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift }} // TODO: fields
{% endfor %}
default: throw InternalError.unexpectedEnumCase
}
}

func write(into buf: Writer) {
self.lower().write(into: buf)
}

func lower() -> UInt32 {
switch self {
{% for variant in e.variants() %}
case .{{ variant|enum_variant_swift }}: return {{ loop.index }}
case .{{ variant.name()|enum_variant_swift }}: buf.putInt({{ loop.index }}) // TODO: fields
{% endfor %}
}
}
Expand Down
101 changes: 98 additions & 3 deletions uniffi_bindgen/src/interface/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//! all handled by a single abstraction. This might need to be refactored in future
//! if we grow significantly more complicated attribute handling.
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};

use anyhow::{bail, Result};

Expand All @@ -27,6 +27,7 @@ use anyhow::{bail, Result};
pub(super) enum Attribute {
ByRef,
Error,
TaggedUnion,
Threadsafe,
Throws(String),
}
Expand All @@ -49,6 +50,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute {
weedle::attribute::ExtendedAttribute::NoArgs(attr) => match (attr.0).0 {
"ByRef" => Ok(Attribute::ByRef),
"Error" => Ok(Attribute::Error),
"TaggedUnion" => Ok(Attribute::TaggedUnion),
"Threadsafe" => Ok(Attribute::Threadsafe),
_ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0),
},
Expand Down Expand Up @@ -129,6 +131,16 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes {
}
}

impl<T: TryInto<EnumAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for EnumAttributes {
type Error = anyhow::Error;
fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
match value {
None => Ok(Default::default()),
Some(v) => v.try_into(),
}
}
}

/// Represents UDL attributes that might appear on a function.
///
/// This supports the `[Throws=ErrorName]` attribute for functions that
Expand Down Expand Up @@ -163,6 +175,18 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttribut
}
}

impl<T: TryInto<FunctionAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
for FunctionAttributes
{
type Error = anyhow::Error;
fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
match value {
None => Ok(Default::default()),
Some(v) => v.try_into(),
}
}
}

/// Represents UDL attributes that might appear on a function argument.
///
/// This supports the `[ByRef]` attribute for arguments that should be passed
Expand All @@ -189,11 +213,29 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ArgumentAttribut
}
}

impl<T: TryInto<ArgumentAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
for ArgumentAttributes
{
type Error = anyhow::Error;
fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
match value {
None => Ok(Default::default()),
Some(v) => v.try_into(),
}
}
}

/// Represents UDL attributes that might appear on an `interface` definition.
#[derive(Debug, Clone, Hash, Default)]
pub(super) struct InterfaceAttributes(Vec<Attribute>);

impl InterfaceAttributes {
pub fn tagged_union(&self) -> bool {
self.0
.iter()
.any(|attr| matches!(attr, Attribute::TaggedUnion))
}

pub fn threadsafe(&self) -> bool {
self.0
.iter()
Expand All @@ -207,13 +249,29 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for InterfaceAttribu
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
Attribute::TaggedUnion => Ok(()),
Attribute::Threadsafe => Ok(()),
_ => bail!(format!("{:?} not supported for interface classes", attr)),
_ => bail!(format!("{:?} not supported for interface definition", attr)),
})?;
if attrs.len() > 1 {
bail!("conflicting attributes on interface definition");
}
Ok(Self(attrs))
}
}

impl<T: TryInto<InterfaceAttributes, Error = anyhow::Error>> TryFrom<Option<T>>
for InterfaceAttributes
{
type Error = anyhow::Error;
fn try_from(value: Option<T>) -> Result<Self, Self::Error> {
match value {
None => Ok(Default::default()),
Some(v) => v.try_into(),
}
}
}

// There may be some divergence between Methods and Functions at some point,
// but not yet.
pub(super) type MethodAttributes = FunctionAttributes;
Expand Down Expand Up @@ -245,13 +303,22 @@ mod test {
Ok(())
}

#[test]
fn test_tagged_union() -> Result<()> {
let (_, node) = weedle::attribute::ExtendedAttribute::parse("TaggedUnion").unwrap();
let attr = Attribute::try_from(&node)?;
assert!(matches!(attr, Attribute::TaggedUnion));
Ok(())
}

#[test]
fn test_threadsafe() -> Result<()> {
let (_, node) = weedle::attribute::ExtendedAttribute::parse("Threadsafe").unwrap();
let attr = Attribute::try_from(&node)?;
assert!(matches!(attr, Attribute::Threadsafe));
Ok(())
}

#[test]
fn test_throws() -> Result<()> {
let (_, node) = weedle::attribute::ExtendedAttribute::parse("Throws=Name").unwrap();
Expand Down Expand Up @@ -360,12 +427,40 @@ mod test {
Ok(())
}

#[test]
fn test_tagged_union_attribute() -> Result<()> {
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[TaggedUnion]").unwrap();
let attrs = InterfaceAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.tagged_union(), true));

let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
let attrs = InterfaceAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.tagged_union(), false));

let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Threadsafe]").unwrap();
let attrs = InterfaceAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.tagged_union(), false));

let (_, node) =
weedle::attribute::ExtendedAttributeList::parse("[Threadsafe, TaggedUnion]").unwrap();
let err = InterfaceAttributes::try_from(&node).unwrap_err();
assert_eq!(
err.to_string(),
"conflicting attributes on interface definition"
);

Ok(())
}

#[test]
fn test_other_attributes_not_supported_for_interfaces() -> Result<()> {
let (_, node) =
weedle::attribute::ExtendedAttributeList::parse("[Threadsafe, Error]").unwrap();
let err = InterfaceAttributes::try_from(&node).unwrap_err();
assert_eq!(err.to_string(), "Error not supported for interface classes");
assert_eq!(
err.to_string(),
"Error not supported for interface definition"
);
Ok(())
}
}
Loading

0 comments on commit 45554da

Please sign in to comment.