mod emitter;
mod family;
mod lower;
mod nodes;
mod value;
use proc_macro2::{Ident, Span, TokenStream};
use crate::Views;
use crate::decl;
use crate::model::Library;
use crate::{Externs, TokenEnum, error::Error};
pub use nodes::nodes;
pub fn emit(
model: &Library,
externs: &Externs,
schema: &str,
views: Views,
enums: &[TokenEnum],
) -> Result<String, Error> {
let lowered = match views {
Views::Generate => lower::library(model, externs, enums)?,
Views::Skip => lower::tokens(model, enums)?,
};
let declarations = decl::with_family(model, family::declarations)?;
render(emitter::library(&lowered, &declarations), schema)
}
pub(super) mod items {
pub const TOKENS: &str = "tokens";
pub const LIBRARY_NAME: &str = "LIBRARY_NAME";
pub const SCHEMAS: &str = "SCHEMAS";
pub const PRIM_METADATA: &str = "PrimMetadata";
pub const ATTRIBUTE_METADATA: &str = "AttributeMetadata";
pub const RELATIONSHIP_METADATA: &str = "RelationshipMetadata";
pub const STAGE_METADATA: &str = "StageMetadata";
pub const ALL: [&str; 7] = [
TOKENS,
LIBRARY_NAME,
SCHEMAS,
PRIM_METADATA,
ATTRIBUTE_METADATA,
RELATIONSHIP_METADATA,
STAGE_METADATA,
];
}
fn doc_lines(text: &str) -> impl Iterator<Item = String> + '_ {
text.lines().map(|line| format!(" {line}"))
}
fn ident(name: &str) -> Ident {
Ident::new(name, Span::call_site())
}
fn path_text(path: &syn::Path) -> String {
let leading = match path.leading_colon.is_some() {
true => "::",
false => "",
};
let segments: Vec<String> = path.segments.iter().map(|segment| segment.ident.to_string()).collect();
format!("{leading}{}", segments.join("::"))
}
fn within(home: &syn::Path, name: &str) -> String {
format!("{}::{name}", path_text(home))
}
const GENERATED_BY: &str = "WARNING: THIS FILE IS GENERATED BY openusd-build. DO NOT EDIT.";
fn render(file: TokenStream, schema: &str) -> Result<String, Error> {
let parsed: syn::File = syn::parse2(file).map_err(|cause| Error::Malformed {
schema: schema.to_owned(),
cause: cause.to_string(),
})?;
let items: Vec<String> = parsed
.items
.into_iter()
.map(|item| {
let one = syn::File {
shebang: None,
frontmatter: None,
attrs: Vec::new(),
items: vec![item],
};
prettyplease::unparse(&one)
})
.collect();
Ok(format!(
"// {GENERATED_BY}\n// Generated from {schema}.\n\n{}",
items.join("\n")
))
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::Path;
use std::sync::LazyLock;
use super::*;
use crate::configure;
use crate::error::Error;
use crate::tests::{TAG_API, read_source, schema};
use crate::validate::Violation;
use crate::{TokenEnum, TokenEnumError};
const KINDS: &str = r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testEmit"
}
)
{
}
class "Typed" {}
class "APISchemaBase" {}
class "Shape" (
inherits = </Typed>
) {
color3f color = (1, 1, 1)
}
class Ball "Ball" (
inherits = </Shape>
) {
double radius = 1
}
class "TagAPI" (
inherits = </APISchemaBase>
customData = {
token apiSchemaType = "singleApply"
}
) {
string tag = ""
}
class "SlotAPI" (
inherits = </APISchemaBase>
customData = {
token apiSchemaType = "multipleApply"
token propertyNamespacePrefix = "slot"
}
) {
int depth = 0
}
"#;
const ACCESSORS: &str = r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testAccessors"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
) {
double radius = 1 (
doc = "The radius."
)
uniform token mode = "a" (
allowedTokens = ["a", "b"]
)
custom float temp
rel target
int hidden (
customData = {
string apiName = ""
}
)
}
"#;
const VERSIONED_ROOT: &str = r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testRoot"
}
)
{
}
class "Typed" {}
class "Typed_1" {}
"#;
fn impl_block<'a>(text: &'a str, name: &str) -> &'a str {
let opened = format!("impl {name} {{");
let (_, rest) = text
.split_once(&opened)
.unwrap_or_else(|| panic!("{name} has an impl block"));
let (body, _) = rest
.split_once(
"
}",
)
.unwrap_or_else(|| panic!("{name}'s impl block ends"));
body
}
const REFERENCES: &str = r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testRefs"
}
)
{
}
class "Typed" {}
class RefsCrate "RefsCrate" (
doc = """A TestRefsCrate holds a TestRefsBox.
See GetWeightAttr(), GetSizeAttr() and TestRefsBox::GetSizeAttr()."""
inherits = </Typed>
customData = {
string className = "Crate"
}
) {
}
class Tin "Tin" (
doc = "Another thing with a size."
inherits = </Typed>
) {
double size = 1
}
class Box "Box" (
doc = """A TestRefsBox, which knows its own size.
\\sa GetSizeAttr()
\\sa ComputeNothing()"""
inherits = </Typed>
) {
double size = 1
double weight = 1
}
"#;
fn packed_accessors() -> String {
packed(accessors())
}
fn packed(text: &str) -> String {
text.chars().filter(|c| !c.is_whitespace()).collect()
}
fn emitted(source: &str) -> String {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(dir.path(), source).expect("resolves");
emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits")
}
fn kinds() -> &'static str {
static ONCE: LazyLock<String> = LazyLock::new(|| emitted(KINDS));
&ONCE
}
fn accessors() -> &'static str {
static ONCE: LazyLock<String> = LazyLock::new(|| emitted(ACCESSORS));
&ONCE
}
fn references() -> &'static str {
static ONCE: LazyLock<String> = LazyLock::new(|| emitted(REFERENCES));
&ONCE
}
fn values() -> &'static str {
static ONCE: LazyLock<String> = LazyLock::new(|| {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/values");
let library = configure()
.search_path(&dir)
.read(&dir.join("schema.usda"), &[])
.map(|(library, _)| library)
.expect("the values fixture resolves");
emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits")
});
&ONCE
}
#[test]
fn every_value_written() {
let text = values();
for expected in [
"::openusd::sdf::Dictionary::from(",
"::openusd::sdf::Value::DoubleVec(",
"::openusd::gf::Mat2d([",
"::openusd::gf::Mat3d([",
"::openusd::gf::Matrix4d([",
"::openusd::gf::quath(",
"::openusd::sdf::TimeCode(",
"::openusd::gf::f16::from_bits(",
"::openusd::sdf::AssetPath::new(",
"::openusd::sdf::Value::ValueBlock",
"::openusd::usd::PropertyDecl::relationship(\"plain\")",
"::openusd::usd::PropertyDecl::relationship(\"loose\").varying()",
] {
assert!(text.contains(expected), "{expected} is missing from:\n{text}");
}
}
#[test]
fn kinds_take_their_shapes() {
let text = kinds();
assert!(
text.contains("pub trait ShapeSchema: ::openusd::usd::Typed {"),
"{text}"
);
assert!(text.contains("pub trait BallSchema: ShapeSchema {"), "{text}");
assert!(text.contains("pub struct Shape(::openusd::usd::Prim);"), "{text}");
assert!(text.contains("pub struct Ball(::openusd::usd::Prim);"), "{text}");
assert!(text.contains("impl BallSchema for Ball {}"), "{text}");
assert!(text.contains("impl ShapeSchema for Ball {}"), "{text}");
assert!(text.contains("impl ::openusd::usd::Typed for Shape {}"), "{text}");
}
#[test]
fn abstract_constructors() {
let text = kinds();
assert!(
text.contains("prim.is_a(tokens::SHAPE)?.then_some(Self(prim))"),
"{text}"
);
assert!(!impl_block(text, "Shape").contains("fn define"), "{text}");
}
#[test]
fn concrete_constructors() {
let text = kinds();
assert!(text.contains("stage.define_typed_prim(path, tokens::BALL)?"), "{text}");
assert!(
text.contains("prim.is_a(tokens::BALL)?.then_some(Self(prim))"),
"{text}"
);
assert!(text.contains("Self::from_prim(stage.prim(path)?)"), "{text}");
}
#[test]
fn applied_constructors() {
let text = kinds();
assert!(text.contains("prim.clone().apply_api(tokens::TAG_API)?"), "{text}");
assert!(
text.contains("prim.has_api_schema(tokens::TAG_API)?.then_some(Self(prim))"),
"{text}"
);
assert!(text.contains("name: ::openusd::tf::Token,"), "{text}");
assert!(text.contains("make_applied_name(tokens::SLOT_API"), "{text}");
}
#[test]
fn instanced_accessors() {
let text = kinds();
assert!(
text.contains("make_multiple_apply_name_instance("),
"the template is instantiated: {text}"
);
assert!(text.contains("tokens::SLOT_MULTIPLE_APPLY_TEMPLATE_DEPTH"), "{text}");
assert!(text.contains("self.name.as_str()"), "{text}");
}
#[test]
fn instances_decode_from_a_path() {
let text = packed(kinds());
assert!(
text.contains(r#"is_schema_property_base_name(base_name:&str)->bool{::std::matches!(base_name,"depth")"#),
"the names it declares are the ones an instance may not end with: {text}"
);
assert!(
text.contains(r#"property.strip_prefix("slot")?.strip_prefix(':')?"#),
"an instance is what follows the schema's namespace: {text}"
);
assert!(!impl_block(kinds(), "TagAPI").contains("instance_at_path"), "{text}");
}
#[test]
fn no_imports() {
let text = kinds();
assert!(!text.contains("\nuse "), "{text}");
}
#[test]
fn schema_data_is_declared() {
let text = kinds();
assert!(text.contains("pub const LIBRARY_NAME: &str = \"testEmit\";"), "{text}");
assert!(
text.contains("pub const SCHEMAS: &::openusd::usd::SchemaFamily<'static>"),
"{text}"
);
assert!(text.contains("::openusd::usd::SchemaDecl::new("), "{text}");
assert!(text.contains("::openusd::usd::SchemaKind::ConcreteTyped"), "{text}");
assert!(
text.contains("::openusd::usd::PropertyDecl::attribute(\"radius\", \"double\")"),
"{text}"
);
assert!(!text.contains("include_bytes!"), "{text}");
}
#[test]
fn attribute_pair() {
let text = accessors();
assert!(
text.contains("fn radius_attr(&self) -> ::openusd::usd::Attribute {"),
"{text}"
);
assert!(text.contains("self.prim().attribute(tokens::RADIUS)"), "{text}");
assert!(text.contains("::openusd::sdf::ValueTypeName::DOUBLE"), "{text}");
}
#[test]
fn creator_authors_declaration() {
let text = packed_accessors();
assert!(
text.contains("attribute_builder(tokens::RADIUS,::openusd::sdf::ValueTypeName::DOUBLE).custom(false)}"),
"a schema's property is not custom, and varies by default: {text}"
);
assert!(
text.contains("attribute_builder(tokens::MODE,::openusd::sdf::ValueTypeName::TOKEN).custom(false).variability(::openusd::sdf::Variability::Uniform)}"),
"a uniform property says so: {text}"
);
assert!(
text.contains("attribute_builder(tokens::TEMP,::openusd::sdf::ValueTypeName::FLOAT)}"),
"a `custom` property takes the defaults: {text}"
);
assert!(
text.contains("::std::result::Result::Ok(self.radius_attr_builder().build()?)"),
"the creator is that declaration, authored: {text}"
);
}
#[test]
fn relationship_pair() {
let text = accessors();
assert!(
text.contains("fn target_rel(&self) -> ::openusd::usd::Relationship {"),
"{text}"
);
assert!(
packed_accessors().contains("relationship_builder(tokens::TARGET).custom(false)}"),
"{text}"
);
assert!(
packed_accessors().contains("::std::result::Result::Ok(self.target_rel_builder().build()?)"),
"{text}"
);
}
const TOKEN_ENUMS: &str = r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testTokenEnums"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
) {
uniform token mode = "a" (
allowedTokens = ["a", "b"]
doc = "Which way it runs."
)
uniform token axis = "X" (
allowedTokens = ["X", "Y", "Z"]
)
uniform token style (
allowedTokens = ["", "plain"]
)
uniform token phase (
allowedTokens = ["start", "end"]
)
uniform token outcome = "ok" (
allowedTokens = ["ok", "error"]
)
double radius = 1
}
"#;
fn with_enums(enums: Vec<TokenEnum>) -> Result<String, Error> {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(dir.path(), TOKEN_ENUMS).expect("the schema reads");
emit(&model, &Externs::new(), "schema.usda", Views::Generate, &enums)
}
#[test]
fn token_enum_over_allowed_tokens() {
let text = with_enums(vec![TokenEnum::new("Mode", "testTokenEnums", "Held.mode")]).expect("generates");
assert!(text.contains("pub enum Mode {"), "{text}");
assert!(text.contains("A,"), "{text}");
assert!(text.contains("B,"), "{text}");
assert!(text.contains("Self::A => tokens::A"), "{text}");
assert!(
text.contains("tokens::A => ::std::option::Option::Some(Self::A)"),
"{text}"
);
assert!(!text.contains("pub enum Axis"), "{text}");
assert!(!text.contains("pub enum Style"), "{text}");
}
#[test]
fn default_is_opt_in() {
let plain = with_enums(vec![TokenEnum::new("Mode", "testTokenEnums", "Held.mode")]).expect("generates");
assert!(!plain.contains("::std::default::Default"), "{plain}");
assert!(!plain.contains("#[default]"), "{plain}");
let defaulted = with_enums(vec![
TokenEnum::new("Mode", "testTokenEnums", "Held.mode").with_default(),
])
.expect("generates");
assert!(defaulted.contains("::std::default::Default"), "{defaulted}");
assert!(defaulted.contains("#[default]"), "{defaulted}");
}
#[test]
fn default_without_fallback_rejected() {
let error = with_enums(vec![
TokenEnum::new("Phase", "testTokenEnums", "Held.phase").with_default(),
])
.expect_err("phase declares no fallback");
assert!(
matches!(
error,
Error::TokenEnum {
cause: TokenEnumError::NoFallback { .. },
..
}
),
"{error}"
);
}
#[test]
fn unnameable_token_rejected() {
let error =
with_enums(vec![TokenEnum::new("Style", "testTokenEnums", "Held.style")]).expect_err("`\"\"` has no name");
assert!(
matches!(
error,
Error::TokenEnum {
cause: TokenEnumError::UnnameableToken { .. },
..
}
),
"{error}"
);
let text = with_enums(vec![
TokenEnum::new("Style", "testTokenEnums", "Held.style").variant("", "Unset"),
])
.expect("generates with the name the caller gave");
assert!(text.contains("Unset"), "{text}");
}
#[test]
fn conversions_come_from_the_core() {
let text = with_enums(vec![TokenEnum::new("Outcome", "testTokenEnums", "Held.outcome")]).expect("generates");
assert!(text.contains("Error,"), "the token is still a variant: {text}");
assert!(text.contains("::openusd::sdf::impl_token_value!(Outcome);"), "{text}");
assert!(!text.contains("impl ::std::convert::TryFrom"), "{text}");
}
#[test]
fn reserved_name_rejected() {
for name in items::ALL {
let error = with_enums(vec![TokenEnum::new(name, "testTokenEnums", "Held.mode")]).expect_err(name);
assert!(
matches!(
error,
Error::TokenEnum {
cause: TokenEnumError::ReservedName,
..
}
),
"{name}: {error}"
);
}
}
#[test]
fn unknown_source_rejected() {
for (source, expected) in [
("Nope.mode", "class `Nope`"),
("Held.nope", "`Held.nope`"),
("Held.radius", "allowedTokens"),
("Held", "Class.property"),
] {
let error = with_enums(vec![TokenEnum::new("Mode", "testTokenEnums", source)]).expect_err(source);
assert!(error.to_string().contains(expected), "{source}: {error}");
}
}
#[test]
fn suppressed_has_none() {
let text = accessors();
assert!(!text.contains("fn hidden"), "{text}");
}
#[test]
fn typed_read_emitted() {
let text = packed_accessors();
assert!(
text.contains(
"fnradius(&self)->::openusd::Result<::std::option::Option<f64>>{self.radius_attr().get::<f64>()}"
),
"{text}"
);
assert!(
text.contains(
"fnradius_at(&self,time:impl::std::convert::Into<::std::option::Option<::openusd::usd::TimeCode>>,)->::openusd::Result<::std::option::Option<f64>>{self.radius_attr().get_at::<f64>(time)}"
),
"{text}"
);
}
#[test]
fn uniform_read_untimed() {
let text = packed_accessors();
assert!(
text.contains("fnmode(&self)->::openusd::Result<::std::option::Option<::openusd::tf::Token>>"),
"{text}"
);
assert!(!text.contains("fnmode_at("), "{text}");
assert!(!text.contains("fntarget(") && !text.contains("fntarget_at("), "{text}");
}
#[test]
fn root_method_kept() {
let text = packed(&emitted(&schema(
"testRoot",
r#"class "Thing" (
inherits = </Typed>
) {
string path = ""
}"#,
)));
assert!(!text.contains("fnpath(") && !text.contains("fnpath_at("), "{text}");
assert!(text.contains("fnpath_attr(&self)"), "{text}");
}
#[test]
fn constructor_name_kept() {
let text = packed(&emitted(&schema(
"testCtor",
r#"class "GetAPI" (
inherits = </APISchemaBase>
customData = { token apiSchemaType = "singleApply" }
) {
string get = ""
}"#,
)));
assert_eq!(text.matches("pubfnget(").count(), 1, "the constructor alone: {text}");
assert!(!text.contains("fnget_at("), "{text}");
assert!(text.contains("fnget_attr(&self)"), "{text}");
}
#[test]
fn keyword_read_escaped() {
let text = emitted(&schema(
"testKeyword",
r#"class "Thing" (
inherits = </Typed>
) {
token type = "a"
}"#,
));
let text = packed(&text);
assert!(text.contains("fntype_(&self)"), "{text}");
assert!(text.contains("fntype_at(&self"), "{text}");
assert!(text.contains("fntype_attr(&self)"), "{text}");
}
#[test]
fn documents_the_declaration() {
let text = accessors();
assert!(text.contains("The radius."), "{text}");
assert!(text.contains("Declared `double radius = 1.0`."), "{text}");
assert!(
text.contains("Declared `uniform token mode = \"a\"`. One of `a`, `b`."),
"{text}"
);
}
#[test]
fn two_ids_one_constant() {
let dir = tempfile::tempdir().expect("tempdir");
let library = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testConstants"
dictionary libraryTokens = {
dictionary drawMode = {
string value = "a"
}
dictionary draw_mode = {
string value = "b"
}
}
}
)
{
}
class "Typed" {}
"#,
)
.expect("the library itself is well formed");
let text = emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("both are named");
assert!(text.contains("pub const DRAW_MODE: &str = \"b\";"), "{text}");
assert!(text.contains("pub const DRAW_MODE_: &str = \"a\";"), "{text}");
}
#[test]
fn two_properties_one_method() {
let dir = tempfile::tempdir().expect("tempdir");
let library = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testMethods"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
) {
int one (
customData = {
string apiName = "same"
}
)
int two (
customData = {
string apiName = "same"
}
)
}
"#,
)
.expect("the library itself is well formed");
match emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::MethodCollision { method, .. },
..
}) => assert_eq!(method, "same_attr"),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn keyword_class_name() {
let dir = tempfile::tempdir().expect("tempdir");
let library = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testKeywords"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
customData = {
string className = "type"
}
) {
}
"#,
)
.expect("the library itself is well formed");
match emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::NotAnIdentifier { name },
..
}) => assert_eq!(name, "type"),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn roots_are_not_generated() {
let text = kinds();
assert!(!text.contains("trait Typed"), "{text}");
assert!(!text.contains("trait APISchemaBase"), "{text}");
assert!(
text.contains("::openusd::usd::Typed"),
"the core's is what a view answers to: {text}"
);
}
#[test]
fn references_become_links() {
let text = references();
assert!(text.contains("[`size_attr`](BoxSchema::size_attr)"), "{text}");
assert!(text.contains("[`Box`]"), "{text}");
assert!(text.contains("`ComputeNothing()`"), "{text}");
}
#[test]
fn class_name_builds_symbol() {
let text = references();
assert!(text.contains("A [`Crate`] holds a [`Box`]."), "{text}");
}
#[test]
fn library_answers_unique_names() {
let text = references();
let at = text.find("See [").expect("the sentence that references them");
let rest = &text[at..];
let sentence = rest.split_once("\n ///\n").map_or(rest, |(paragraph, _)| paragraph);
assert!(
sentence.contains("[`weight_attr`](BoxSchema::weight_attr)"),
"{sentence}"
);
assert!(
sentence.contains("`GetSizeAttr()`"),
"ambiguous, so left alone: {sentence}"
);
assert!(
sentence.contains("[`size_attr`](BoxSchema::size_attr)"),
"qualified, so answered: {sentence}"
);
}
#[test]
fn versioned_root_not_generated() {
let text = emitted(VERSIONED_ROOT);
assert!(text.contains("pub const LIBRARY_NAME"), "the library emits: {text}");
assert!(!text.contains("pub trait"), "{text}");
assert!(!text.contains("pub struct"), "{text}");
}
fn stacked(dir: &Path, layers: &[(&str, &str)], root: &str) -> Library {
for (name, contents) in layers {
fs::write(dir.join(name), contents).expect("writes a layer");
}
read_source(dir, root).expect("resolves")
}
fn sublayering(layers: &[&str], classes: &str) -> String {
let layers: Vec<String> = layers.iter().map(|layer| format!(" @{layer}@")).collect();
format!(
r#"#usda 1.0
(
subLayers = [
{}
]
)
def "GLOBAL" (
customData = {{
string libraryName = "testHere"
}}
)
{{
}}
{classes}
"#,
layers.join(",\n")
)
}
fn with_doc(doc: &str) -> String {
format!(
r#"class Crate "Crate" (
inherits = </Typed>
doc = "{doc}"
) {{
}}"#
)
}
fn inheriting(dir: &Path) -> Library {
let base = schema(
"testElsewhere",
r#"class "Thing" (
inherits = </Typed>
) {
}"#,
);
stacked(
dir,
&[("base.usda", base.as_str())],
&sublayering(
&["base.usda"],
r#"class Crate "Crate" (
inherits = </Thing>
) {
}"#,
),
)
}
#[test]
fn library_path_must_read() {
let dir = tempfile::tempdir().expect("tempdir");
let model = inheriting(dir.path());
let externs = Externs::from([("testElsewhere".to_owned(), "not a path!".to_owned())]);
match emit(&model, &externs, "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::UnreadableLibraryPath { library, path },
..
}) => {
assert_eq!(library, "testElsewhere");
assert_eq!(path, "not a path!");
}
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn library_must_be_placed() {
let dir = tempfile::tempdir().expect("tempdir");
let model = inheriting(dir.path());
match emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::UnknownLibrary { library, .. },
..
}) => assert_eq!(library, "testElsewhere"),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn unreached_library_ignored() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(dir.path(), KINDS).expect("resolves");
let externs = Externs::from([("testElsewhere".to_owned(), "not a path!".to_owned())]);
emit(&model, &externs, "schema.usda", Views::Generate, &[]).expect("emits");
}
fn reflecting_elsewhere() -> Library {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/reflect");
configure()
.search_path(&dir)
.read(&dir.join("schema.usda"), &[])
.map(|(library, _)| library)
.expect("the reflect fixture resolves")
}
#[test]
fn reflected_foreign_emitted() {
let model = reflecting_elsewhere();
let externs = Externs::from([("testElsewhere".to_owned(), "crate::elsewhere".to_owned())]);
let rust = emit(&model, &externs, "schema.usda", Views::Generate, &[]).expect("emits");
assert!(
rust.contains("fn thing_api(&self) -> crate::elsewhere::ThingAPI"),
"{rust}"
);
assert!(
rust.contains("crate::elsewhere::ThingAPI::from_prim_unchecked"),
"{rust}"
);
assert!(rust.contains("fn weight_attr(&self)"), "{rust}");
assert!(rust.contains("fn create_owner_rel(&self)"), "{rust}");
assert!(rust.contains("tokens::WEIGHT"), "{rust}");
}
#[test]
fn reflected_library_unplaced() {
let model = reflecting_elsewhere();
match emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::UnknownLibrary { library, class },
..
}) => {
assert_eq!(library, "testElsewhere");
assert_eq!(class.as_str(), "ThingAPI");
}
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn reflected_first_wins() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(
dir.path(),
&schema(
"testFirst",
&format!(
r#"{TAG_API}
class "LabelAPI" (
inherits = </APISchemaBase>
customData = {{ token apiSchemaType = "singleApply" }}
) {{
string label = ""
int dup = 0
}}
class "Thing" (
inherits = </Typed>
prepend apiSchemas = ["TagAPI", "LabelAPI"]
customData = {{ token[] reflectedAPISchemas = ["TagAPI", "LabelAPI"] }}
) {{
}}"#
),
),
)
.expect("resolves");
let rust = emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits");
let start = rust
.find("pub trait ThingSchema")
.expect("the reflecting class's trait");
let body = &rust[start..];
let body = &body[..body.find("\n}\n").expect("the trait ends")];
assert_eq!(body.matches("fn dup_attr(").count(), 1, "{body}");
assert!(body.contains("fn tag_attr("), "{body}");
assert!(body.contains("fn label_attr("), "{body}");
}
#[test]
fn reflected_own_collides() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(
dir.path(),
&schema(
"testCollide",
&format!(
r#"{TAG_API}
class "Thing" (
inherits = </Typed>
prepend apiSchemas = ["TagAPI"]
customData = {{ token[] reflectedAPISchemas = ["TagAPI"] }}
) {{
string tag = "own"
}}"#
),
),
)
.expect("resolves");
match emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::MethodCollision { method, first, second },
..
}) => {
assert_eq!(method, "tag_attr");
assert_eq!(first.as_str(), "tag");
assert_eq!(second.as_str(), "TagAPI");
}
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
fn reflecting_base(derived: &str) -> String {
schema(
"testAncestor",
&format!(
r#"{TAG_API}
class "Base" (
inherits = </Typed>
prepend apiSchemas = ["TagAPI"]
customData = {{ token[] reflectedAPISchemas = ["TagAPI"] }}
) {{
}}
{derived}"#
),
)
}
fn trait_body<'a>(rust: &'a str, name: &str) -> &'a str {
let start = rust.find(&format!("pub trait {name}")).expect("the trait");
let body = &rust[start..];
&body[..body.find("\n}\n").expect("the trait ends")]
}
#[test]
fn reflected_by_ancestor() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(
dir.path(),
&reflecting_base(
r#"class "Derived" (
inherits = </Base>
customData = { token[] reflectedAPISchemas = ["TagAPI"] }
) {
}"#,
),
)
.expect("resolves");
let rust = emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits");
let base = trait_body(&rust, "BaseSchema");
assert!(base.contains("fn tag_attr(") && base.contains("fn tag_api("), "{base}");
let derived = trait_body(&rust, "DerivedSchema");
assert!(
!derived.contains("fn tag_attr(") && !derived.contains("fn tag_api("),
"{derived}"
);
}
#[test]
fn reflected_by_ancestor_collides() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(
dir.path(),
&reflecting_base(
r#"class "Derived" (
inherits = </Base>
) {
string tag = "own"
}"#,
),
)
.expect("resolves");
match emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::MethodCollision { method, first, second },
..
}) => {
assert_eq!(method, "tag_attr");
assert_eq!(first.as_str(), "tag");
assert_eq!(second.as_str(), "Base");
}
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn reflected_listed_twice() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(
dir.path(),
&schema(
"testTwice",
&format!(
r#"{TAG_API}
class "Thing" (
inherits = </Typed>
prepend apiSchemas = ["TagAPI"]
customData = {{ token[] reflectedAPISchemas = ["TagAPI", "TagAPI"] }}
) {{
}}"#
),
),
)
.expect("resolves");
let rust = emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits");
let thing = trait_body(&rust, "ThingSchema");
assert_eq!(thing.matches("fn tag_api(").count(), 1, "{thing}");
assert_eq!(thing.matches("fn tag_attr(").count(), 1, "{thing}");
}
fn below_foreign_base(dir: &Path, classes: &str) -> Library {
let base = schema(
"testElsewhere",
&format!(
r#"{TAG_API}
class "Base" (
inherits = </Typed>
prepend apiSchemas = ["TagAPI"]
customData = {{ token[] reflectedAPISchemas = ["TagAPI"] }}
) {{
string label = ""
}}"#
),
);
stacked(
dir,
&[("base.usda", base.as_str())],
&sublayering(&["base.usda"], classes),
)
}
fn elsewhere_placed() -> Externs {
Externs::from([("testElsewhere".to_owned(), "crate::elsewhere".to_owned())])
}
fn collision(model: &Library, externs: &Externs) -> (String, String, String) {
match emit(model, externs, "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::MethodCollision { method, first, second },
..
}) => (method, first.to_string(), second.to_string()),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn reflected_by_foreign_ancestor() {
let dir = tempfile::tempdir().expect("tempdir");
let model = below_foreign_base(
dir.path(),
r#"class "Derived" (
inherits = </Base>
customData = { token[] reflectedAPISchemas = ["TagAPI"] }
) {
}"#,
);
let rust = emit(&model, &elsewhere_placed(), "schema.usda", Views::Generate, &[]).expect("emits");
let derived = trait_body(&rust, "DerivedSchema");
assert!(
derived.starts_with("pub trait DerivedSchema: crate::elsewhere::BaseSchema"),
"{derived}"
);
assert!(
!derived.contains("fn tag_attr(") && !derived.contains("fn tag_api("),
"{derived}"
);
}
#[test]
fn reflected_by_foreign_ancestor_collides() {
let dir = tempfile::tempdir().expect("tempdir");
let model = below_foreign_base(
dir.path(),
r#"class "Derived" (
inherits = </Base>
) {
string tag = "own"
}"#,
);
let inherited: Vec<&str> = model.inherited.iter().map(|class| class.identifier.as_str()).collect();
assert_eq!(
inherited,
vec!["Base", "TagAPI"],
"the ancestor and what it reflects are held"
);
let (method, first, second) = collision(&model, &elsewhere_placed());
assert_eq!(
(method.as_str(), first.as_str(), second.as_str()),
("tag_attr", "tag", "Base")
);
}
#[test]
fn foreign_reflections_collide() {
let dir = tempfile::tempdir().expect("tempdir");
let model = below_foreign_base(
dir.path(),
r#"class "LabelAPI" (
inherits = </APISchemaBase>
customData = { token apiSchemaType = "singleApply" }
) {
string tag = ""
}
class "Derived" (
inherits = </Base>
prepend apiSchemas = ["LabelAPI"]
customData = { token[] reflectedAPISchemas = ["LabelAPI"] }
) {
}"#,
);
let (method, first, second) = collision(&model, &elsewhere_placed());
assert_eq!(
(method.as_str(), first.as_str(), second.as_str()),
("tag_attr", "Base", "LabelAPI")
);
}
#[test]
fn foreign_grandparent_collides() {
let dir = tempfile::tempdir().expect("tempdir");
let model = below_foreign_base(
dir.path(),
r#"class "Middle" (
inherits = </Base>
) {
}
class "Derived" (
inherits = </Middle>
) {
string tag = "own"
}"#,
);
let (method, first, second) = collision(&model, &elsewhere_placed());
assert_eq!(
(method.as_str(), first.as_str(), second.as_str()),
("tag_attr", "tag", "Base")
);
}
#[test]
fn foreign_ancestor_accessor_linked() {
let dir = tempfile::tempdir().expect("tempdir");
let model = below_foreign_base(
dir.path(),
r#"class "Derived" (
doc = "See GetLabelAttr()."
inherits = </Base>
) {
}"#,
);
let rust = emit(&model, &elsewhere_placed(), "schema.usda", Views::Generate, &[]).expect("emits");
assert!(
rust.contains("[`label_attr`](crate::elsewhere::BaseSchema::label_attr)"),
"{rust}"
);
}
fn elsewhere() -> String {
schema(
"testElsewhere",
r#"class "BigThing" (
inherits = </Typed>
) {
}"#,
)
}
#[test]
fn foreign_class_linked() {
let dir = tempfile::tempdir().expect("tempdir");
let elsewhere = elsewhere();
let model = stacked(
dir.path(),
&[("base.usda", elsewhere.as_str())],
&sublayering(
&["base.usda"],
&with_doc("Like a TestElsewhereBigThing, or a BigThing."),
),
);
let externs = Externs::from([("testElsewhere".to_owned(), "crate::elsewhere".to_owned())]);
let rust = emit(&model, &externs, "schema.usda", Views::Generate, &[]).expect("emits");
assert!(rust.contains("[`BigThing`](crate::elsewhere::BigThing)"), "{rust}");
assert!(
!rust.contains("TestElsewhereBigThing"),
"the qualified spelling is linked: {rust}"
);
assert!(!rust.contains("a `BigThing`"), "the bare spelling is linked: {rust}");
}
#[test]
fn unplaced_foreign_unlinked() {
let dir = tempfile::tempdir().expect("tempdir");
let elsewhere = elsewhere();
let model = stacked(
dir.path(),
&[("base.usda", elsewhere.as_str())],
&sublayering(&["base.usda"], &with_doc("Like a TestElsewhereBigThing.")),
);
let rust = emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits");
assert!(rust.contains("Like a `TestElsewhereBigThing`."), "{rust}");
assert!(!rust.contains("crate::elsewhere"), "{rust}");
}
#[test]
fn foreign_name_ambiguous() {
let dir = tempfile::tempdir().expect("tempdir");
let alpha = schema(
"testAlpha",
r#"class "Alpha" (
inherits = </Typed>
customData = { string className = "SharedThing" }
) {
}"#,
);
let beta = schema(
"testBeta",
r#"class "Beta" (
inherits = </Typed>
customData = { string className = "SharedThing" }
) {
}"#,
);
let model = stacked(
dir.path(),
&[("alpha.usda", alpha.as_str()), ("beta.usda", beta.as_str())],
&sublayering(
&["alpha.usda", "beta.usda"],
&with_doc("TestAlphaSharedThing and TestBetaSharedThing are each a SharedThing."),
),
);
let externs = Externs::from([
("testAlpha".to_owned(), "crate::alpha".to_owned()),
("testBeta".to_owned(), "crate::beta".to_owned()),
]);
let rust = emit(&model, &externs, "schema.usda", Views::Generate, &[]).expect("emits");
assert!(rust.contains("[`SharedThing`](crate::alpha::SharedThing)"), "{rust}");
assert!(rust.contains("[`SharedThing`](crate::beta::SharedThing)"), "{rust}");
assert!(rust.contains("a `SharedThing`."), "{rust}");
}
#[test]
fn foreign_name_yields_to_own() {
let dir = tempfile::tempdir().expect("tempdir");
let elsewhere = schema(
"testElsewhere",
r#"class "Elsewhere" (
inherits = </Typed>
customData = { string className = "BigThing" }
) {
}"#,
);
let model = stacked(
dir.path(),
&[("base.usda", elsewhere.as_str())],
&sublayering(
&["base.usda"],
&format!(
r#"class "BigThing" (
inherits = </Typed>
) {{
}}
{}"#,
with_doc("Is a BigThing. Not a TestElsewhereBigThing.")
),
),
);
let externs = Externs::from([("testElsewhere".to_owned(), "crate::elsewhere".to_owned())]);
let rust = emit(&model, &externs, "schema.usda", Views::Generate, &[]).expect("emits");
assert!(
rust.contains("Is a [`BigThing`]. Not a [`BigThing`](crate::elsewhere::BigThing)."),
"{rust}"
);
}
#[test]
fn library_prefix_kept() {
let dir = tempfile::tempdir().expect("tempdir");
let model = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testHere"
string libraryPrefix = "Pfx"
}
)
{
}
class "Typed" {}
class Crate "Crate" (
inherits = </Typed>
doc = "A PfxCrate, not a TestHereCrate."
) {
}
"#,
)
.expect("resolves");
let rust = emit(&model, &Externs::new(), "schema.usda", Views::Generate, &[]).expect("emits");
assert!(rust.contains("A [`Crate`], not a `TestHereCrate`."), "{rust}");
}
#[test]
fn class_takes_a_file_name() {
let dir = tempfile::tempdir().expect("tempdir");
let library = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testReserved"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
customData = {
string className = "tokens"
}
) {
}
"#,
)
.expect("the library itself is well formed");
match emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::RustNameCollision { name, .. },
..
}) => assert_eq!(name, "tokens"),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn type_without_a_constant() {
let dir = tempfile::tempdir().expect("tempdir");
let library = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testTypes"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
) {
Transform placement
}
"#,
)
.expect("the type is one a stage resolves");
match emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::UnnameableType { property, .. },
..
}) => assert_eq!(property.as_str(), "placement"),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
#[test]
fn custom_get_on_a_redeclaration() {
let dir = tempfile::tempdir().expect("tempdir");
let library = read_source(
dir.path(),
r#"#usda 1.0
def "GLOBAL" (
customData = {
string libraryName = "testShadowed"
}
)
{
}
class "Typed" {}
class "Held" (
inherits = </Typed>
) {
double size = 1
}
class "Deeper" (
inherits = </Held>
) {
double size = 2 (
customData = {
string apiGetImplementation = "custom"
}
)
}
"#,
)
.expect("the library itself is well formed");
match emit(&library, &Externs::new(), "schema.usda", Views::Generate, &[]) {
Err(Error::Definition {
violation: Violation::CustomGetWithoutAccessor { property },
..
}) => assert_eq!(property.as_str(), "size"),
other => panic!("{:?}", other.map(|_| "emitted")),
}
}
}