Skip to content

Commit 1cbb9bb

Browse files
committed
Add unwrapping and inlining to expand-generics (Rust edition)
1 parent 5a9e910 commit 1cbb9bb

File tree

8 files changed

+129
-38
lines changed

8 files changed

+129
-38
lines changed

compiler-rs/clients_schema/src/builtins.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,30 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use once_cell::sync::Lazy;
19-
2018
use crate::TypeName;
19+
use crate::type_name;
20+
21+
macro_rules! declare_type_name {
22+
($id:ident,$namespace:expr,$name:expr) => {
23+
pub const $id: TypeName = type_name!($namespace, $name);
24+
};
25+
}
26+
27+
declare_type_name!(STRING, "_builtins", "string");
28+
declare_type_name!(BOOLEAN, "_builtins", "boolean");
29+
declare_type_name!(OBJECT, "_builtins", "object");
30+
declare_type_name!(BINARY, "_builtins", "binary");
31+
declare_type_name!(VOID, "_builtins", "void");
32+
declare_type_name!(NUMBER, "_builtins", "number");
33+
declare_type_name!(BYTE, "_builtins", "byte");
34+
declare_type_name!(INTEGER, "_builtins", "integer");
35+
declare_type_name!(LONG, "_builtins", "long");
36+
declare_type_name!(FLOAT, "_builtins", "float");
37+
declare_type_name!(DOUBLE, "_builtins", "double");
38+
declare_type_name!(NULL, "_builtins", "null");
39+
declare_type_name!(DICTIONARY, "_builtins", "Dictionary");
40+
declare_type_name!(USER_DEFINED, "_builtins", "UserDefined");
2141

22-
pub static STRING: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "string"));
23-
pub static BOOLEAN: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "boolean"));
24-
pub static OBJECT: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "object"));
25-
pub static BINARY: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "binary"));
26-
pub static VOID: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "void"));
27-
pub static NUMBER: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "number"));
28-
pub static BYTE: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "byte"));
29-
pub static INTEGER: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "integer"));
30-
pub static LONG: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "long"));
31-
pub static FLOAT: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "float"));
32-
pub static DOUBLE: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "double"));
33-
pub static NULL: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "null"));
34-
pub static DICTIONARY: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "Dictionary"));
35-
pub static USER_DEFINED: Lazy<TypeName> = Lazy::new(|| TypeName::new("_builtins", "UserDefined"));
42+
declare_type_name!(ADDITIONAL_PROPERTIES, "_spec_utils", "AdditionalProperties");
3643

37-
pub static ADDITIONAL_PROPERTIES: Lazy<TypeName> = Lazy::new(|| TypeName::new("_spec_utils", "AdditionalProperties"));
44+
declare_type_name!(WITH_NULL_VALUE, "_spec_utils", "WithNullValue");

compiler-rs/clients_schema/src/lib.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ impl TypeName {
7373
}
7474
}
7575

76+
/// Creates a constant `TypeName` from static strings
77+
#[macro_export]
78+
macro_rules! type_name {
79+
($namespace:expr,$name:expr) => {
80+
TypeName {
81+
namespace: arcstr::literal!($namespace),
82+
name: arcstr::literal!($name),
83+
}
84+
};
85+
}
86+
7687
impl Display for TypeName {
7788
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
7889
write!(f, "{}:{}", self.namespace, self.name)
@@ -93,12 +104,6 @@ pub enum ValueOf {
93104
LiteralValue(LiteralValue),
94105
}
95106

96-
impl ValueOf {
97-
pub fn instance_of(name: TypeName) -> ValueOf {
98-
ValueOf::InstanceOf(InstanceOf::new(name))
99-
}
100-
}
101-
102107
impl From<TypeName> for ValueOf {
103108
fn from(name: TypeName) -> Self {
104109
ValueOf::InstanceOf(InstanceOf::new(name))

compiler-rs/clients_schema/src/transform/expand_generics.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use std::collections::HashMap;
18+
use std::collections::{HashMap, HashSet};
1919

2020
use anyhow::bail;
2121
use indexmap::IndexMap;
@@ -26,22 +26,43 @@ use crate::*;
2626
struct Ctx {
2727
new_types: IndexMap<TypeName, TypeDefinition>,
2828
types_seen: std::collections::HashSet<TypeName>,
29+
config: ExpandConfig,
2930
}
3031

3132
/// Generic parameters of a type
3233
type GenericParams = Vec<TypeName>;
33-
/// Generic arguments for an instanciated generic type
34+
/// Generic arguments for an instantiated generic type
3435
type GenericArgs = Vec<ValueOf>;
3536
/// Mapping from generic arguments to values
3637
type GenericMapping = HashMap<TypeName, ValueOf>;
3738

38-
/// Expand all generics by creating new concrete types for every instanciation of a generic type.
39+
#[derive(Clone, Debug)]
40+
pub struct ExpandConfig {
41+
/// Generic types that will be inlined by replacing them with their definition, propagating generic arguments.
42+
pub unwrap: HashSet<TypeName>,
43+
// Generic types that will be unwrapped by replacing them with their (single) generic parameter.
44+
pub inline: HashSet<TypeName>,
45+
}
46+
47+
impl Default for ExpandConfig {
48+
fn default() -> Self {
49+
ExpandConfig {
50+
unwrap: Default::default(),
51+
inline: HashSet::from([builtins::WITH_NULL_VALUE])
52+
}
53+
}
54+
}
55+
56+
/// Expand all generics by creating new concrete types for every instantiation of a generic type.
3957
///
4058
/// The resulting model has no generics anymore. Top-level generic parameters (e.g. SearchRequest's TDocument) are
4159
/// replaced by user_defined_data.
42-
pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
60+
pub fn expand(model: IndexedModel, config: ExpandConfig) -> anyhow::Result<IndexedModel> {
4361
let mut model = model;
44-
let mut ctx = Ctx::default();
62+
let mut ctx = Ctx {
63+
config,
64+
..Ctx::default()
65+
};
4566

4667
for endpoint in &model.endpoints {
4768
for name in [&endpoint.request, &endpoint.response].into_iter().flatten() {
@@ -317,6 +338,14 @@ pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
317338
return Ok(p.clone());
318339
}
319340

341+
// Inline or unwrap if required by the config
342+
if ctx.config.inline.contains(&inst.typ) {
343+
return inline_generic_type(inst, mappings, model, ctx);
344+
}
345+
if ctx.config.unwrap.contains(&inst.typ) {
346+
return unwrap_generic_type(inst, mappings, model, ctx);
347+
}
348+
320349
// Expand generic parameters, if any
321350
let args = inst
322351
.generics
@@ -346,6 +375,51 @@ pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
346375
}
347376
}
348377

378+
/// Inlines a value of a generic type by replacing it with its definition, propagating
379+
/// generic arguments.
380+
fn inline_generic_type(
381+
value: &InstanceOf,
382+
_mappings: &GenericMapping,
383+
model: &IndexedModel,
384+
ctx: &mut Ctx,
385+
) -> anyhow::Result<ValueOf> {
386+
387+
// It has to be an alias (e.g. WithNullValue)
388+
if let TypeDefinition::TypeAlias(inline_def) = model.get_type(&value.typ)? {
389+
// Create mappings to resolve types in the inlined type's definition
390+
let mut inline_mappings = GenericMapping::new();
391+
for (source, dest) in inline_def.generics.iter().zip(value.generics.iter()) {
392+
inline_mappings.insert(source.clone(), dest.clone());
393+
}
394+
// and expand the inlined type's alias definition
395+
let result = expand_valueof(&inline_def.typ, &inline_mappings, model, ctx)?;
396+
return Ok(result);
397+
} else {
398+
bail!("Expecting inlined type {} to be an alias", &value.typ);
399+
}
400+
}
401+
402+
/// Unwraps a value of a generic type by replacing it with its generic parameter
403+
fn unwrap_generic_type(
404+
value: &InstanceOf,
405+
mappings: &GenericMapping,
406+
model: &IndexedModel,
407+
ctx: &mut Ctx,
408+
) -> anyhow::Result<ValueOf> {
409+
410+
// It has to be an alias (e.g. Stringified)
411+
if let TypeDefinition::TypeAlias(_unwrap_def) = model.get_type(&value.typ)? {
412+
// Expand the inlined type's generic argument (there must be exactly one)
413+
if value.generics.len() != 1 {
414+
bail!("Expecting unwrapped type {} to have exactly one generic parameter", &value.typ);
415+
}
416+
let result = expand_valueof(&value.generics[0], mappings, model, ctx)?;
417+
return Ok(result);
418+
} else {
419+
bail!("Expecting unwrapped type {} to be an alias", &value.typ);
420+
}
421+
}
422+
349423
//---------------------------------------------------------------------------------------------
350424
// Misc
351425
//---------------------------------------------------------------------------------------------
@@ -422,12 +496,12 @@ mod tests {
422496

423497
let schema_json = std::fs::read_to_string("../../output/schema/schema.json")?;
424498
let model: IndexedModel = serde_json::from_str(&schema_json)?;
425-
let model = expand_generics(model)?;
499+
let model = expand(model, ExpandConfig::default())?;
426500

427501
let json_no_generics = serde_json::to_string_pretty(&model)?;
428502

429503
if canonical_json != json_no_generics {
430-
std::fs::create_dir("test-output")?;
504+
std::fs::create_dir_all("test-output")?;
431505
let mut out = std::fs::File::create("test-output/schema-no-generics-canonical.json")?;
432506
out.write_all(canonical_json.as_bytes())?;
433507

compiler-rs/clients_schema/src/transform/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@
1717

1818
//! Utilities to transform API models and common transformations:
1919
//! * filtering according to availability
20+
//! * expand generic types so that the model doesn't contain generic types anymore
2021
2122
mod availability;
2223
mod expand_generics;
2324

2425
use std::collections::HashSet;
2526

26-
use availability::Availability;
27-
2827
use crate::{Availabilities, IndexedModel, TypeName};
2928

3029
/// The working state of a type graph traversal algorithm. It keeps track of the types that
@@ -67,6 +66,7 @@ impl Iterator for Worksheet {
6766
}
6867
}
6968

69+
pub use availability::Availability;
7070
/// Transform a model to only keep the endpoints and types that match a predicate on the `availability`
7171
/// properties.
7272
pub fn filter_availability(
@@ -76,6 +76,8 @@ pub fn filter_availability(
7676
Availability::filter(model, avail_filter)
7777
}
7878

79-
pub fn expand_generics(model: IndexedModel) -> anyhow::Result<IndexedModel> {
80-
expand_generics::expand_generics(model)
79+
80+
pub use expand_generics::ExpandConfig;
81+
pub fn expand_generics(model: IndexedModel, config: ExpandConfig) -> anyhow::Result<IndexedModel> {
82+
expand_generics::expand(model, config)
8183
}

compiler-rs/clients_schema_to_openapi/src/main.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ impl Cli {
8787
},
8888
};
8989

90-
model = clients_schema::transform::expand_generics(model)?;
91-
model = clients_schema::transform::filter_availability(model, filter)?;
90+
use clients_schema::transform::*;
91+
92+
model = expand_generics(model, ExpandConfig::default())?;
93+
model = filter_availability(model, filter)?;
9294
}
9395
}
9496

Binary file not shown.

compiler-rs/compiler-wasm-lib/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use anyhow::bail;
1919
use clients_schema::{Availabilities, Visibility};
2020
use wasm_bindgen::prelude::*;
21+
use clients_schema::transform::ExpandConfig;
2122

2223
#[cfg(all(not(target_arch = "wasm32"), not(feature = "cargo-clippy")))]
2324
compile_error!("To build this crate use `make compiler-wasm-lib`");
@@ -43,7 +44,7 @@ fn convert0(json: &str, flavor: &str) -> anyhow::Result<String> {
4344
};
4445

4546
let mut schema = clients_schema::IndexedModel::from_reader(json.as_bytes())?;
46-
schema = clients_schema::transform::expand_generics(schema)?;
47+
schema = clients_schema::transform::expand_generics(schema, ExpandConfig::default())?;
4748
if let Some(filter) = filter {
4849
schema = clients_schema::transform::filter_availability(schema, filter)?;
4950
}

compiler-rs/openapi_to_clients_schema/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ fn generate_dictionary_def(
360360
typ: ValueOf::InstanceOf(InstanceOf {
361361
typ: builtins::DICTIONARY.clone(),
362362
generics: vec![
363-
ValueOf::instance_of(builtins::STRING.clone()),
363+
ValueOf::from(builtins::STRING.clone()),
364364
match value {
365365
AdditionalProperties::Any(_) => (&builtins::USER_DEFINED).into(),
366366

0 commit comments

Comments
 (0)