Generate multiple components in Rust and C++ (#5449)

When lowering to the LLR, generate one PulbicComponent for each exported
component in the main file

Closes #784
This commit is contained in:
Olivier Goffart 2024-06-24 10:03:19 +02:00 committed by GitHub
parent c89ea56abb
commit 3764312561
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 257 additions and 167 deletions

View file

@ -71,11 +71,6 @@ pub fn generate(
#![allow(unused_variables)]
#![allow(unreachable_code)]
if matches!(doc.root_component.root_element.borrow().base_type, ElementType::Error) {
// empty document, nothing to generate
return Ok(());
}
match format {
#[cfg(feature = "cpp")]
OutputFormat::Cpp(config) => {

View file

@ -326,7 +326,7 @@ mod cpp_ast {
}
use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp};
use crate::langtype::{ElementType, Enumeration, EnumerationValue, NativeClass, Type};
use crate::langtype::{Enumeration, EnumerationValue, NativeClass, Type};
use crate::layout::Orientation;
use crate::llr::{
self, EvaluationContext as llr_EvaluationContext, ParentCtx as llr_ParentCtx,
@ -761,14 +761,6 @@ pub fn generate(
}
}
if matches!(
doc.root_component.root_element.borrow().base_type,
ElementType::Error | ElementType::Global
) {
// empty document, nothing to generate
return file;
}
let llr = llr::lower_to_item_tree::lower_to_item_tree(&doc, compiler_config);
// Forward-declare the root so that sub-components can access singletons, the window, etc.

View file

@ -13,7 +13,7 @@ Some convention used in the generated code:
*/
use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, OperatorClass};
use crate::langtype::{ElementType, Enumeration, EnumerationValue, Type};
use crate::langtype::{Enumeration, EnumerationValue, Type};
use crate::layout::Orientation;
use crate::llr::{
self, EvaluationContext as llr_EvaluationContext, Expression, ParentCtx as llr_ParentCtx,
@ -160,16 +160,12 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
})
.unzip();
if matches!(
doc.root_component.root_element.borrow().base_type,
ElementType::Error | ElementType::Global
) {
// empty document, nothing to generate
return TokenStream::default();
}
let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(&doc, &compiler_config);
if llr.public_components.is_empty() {
return Default::default();
}
let sub_compos = llr
.sub_components
.iter()
@ -213,7 +209,7 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
}
#[allow(unused_imports)]
pub use slint_generated::{#(#compo_ids),* #(,#structs_and_enums_ids)* #(,#globals_ids)* #(,#named_exports)*};
pub use slint_generated::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
#[allow(unused_imports)]
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
}

View file

@ -19,8 +19,6 @@ pub fn lower_to_item_tree(
) -> CompilationUnit {
let mut state = LoweringState::default();
let component = &document.root_component;
let mut globals = Vec::new();
for g in &document.used_types.borrow().globals {
let count = globals.len();
@ -31,22 +29,29 @@ pub fn lower_to_item_tree(
state.sub_components.insert(ByAddress(c.clone()), sc);
}
let sc = lower_sub_component(component, &state, None, &compiler_config);
let public_properties = public_properties(component, &sc.mapping, &state);
let mut item_tree = ItemTree {
tree: make_tree(&state, &component.root_element, &sc, &[]),
root: Rc::try_unwrap(sc.sub_component).unwrap(),
parent_context: None,
};
// For C++ codegen, the root component must have the same name as the public component
item_tree.root.name = component.id.clone();
let public_components = document
.exported_roots()
.map(|component| {
let sc = lower_sub_component(&component, &state, None, &compiler_config);
let public_properties = public_properties(&component, &sc.mapping, &state);
let mut item_tree = ItemTree {
tree: make_tree(&state, &component.root_element, &sc, &[]),
root: Rc::try_unwrap(sc.sub_component).unwrap(),
parent_context: None,
};
// For C++ codegen, the root component must have the same name as the public component
item_tree.root.name = component.id.clone();
PublicComponent {
item_tree,
public_properties,
private_properties: component.private_properties.borrow().clone(),
name: component.id.clone(),
}
})
.collect();
let root = CompilationUnit {
public_components: vec![PublicComponent {
item_tree,
public_properties,
private_properties: component.private_properties.borrow().clone(),
name: component.id.clone(),
}],
public_components,
globals,
sub_components: document
.used_types

View file

@ -44,7 +44,6 @@ pub struct Document {
pub node: Option<syntax_nodes::Document>,
pub inner_components: Vec<Rc<Component>>,
pub inner_types: Vec<Type>,
pub root_component: Rc<Component>,
pub local_registry: TypeRegister,
/// A list of paths to .ttf/.ttc files that are supposed to be registered on
/// startup for custom font use.
@ -165,23 +164,6 @@ impl Document {
let mut exports = Exports::from_node(&node, &inner_components, &local_registry, diag);
exports.add_reexports(reexports, diag);
let root_component = exports
.last_exported_component
.clone()
.or_else(|| {
node.ImportSpecifier()
.last()
.and_then(|import| {
crate::typeloader::ImportedName::extract_imported_names(&import).last()
})
.and_then(|import| local_registry.lookup_element(&import.internal_name).ok())
.and_then(|c| match c {
ElementType::Component(c) => Some(c),
_ => None,
})
})
.unwrap_or_default();
let custom_fonts = foreign_imports
.into_iter()
.filter_map(|import| {
@ -247,7 +229,6 @@ impl Document {
Document {
node: Some(node),
root_component,
inner_components,
inner_types,
local_registry,
@ -258,13 +239,42 @@ impl Document {
}
}
pub fn exported_roots(&self) -> impl Iterator<Item = Rc<Component>> + DoubleEndedIterator + '_ {
let mut iter = self
.exports
.iter()
.filter_map(|e| e.1.as_ref().left())
.filter(|c| !c.is_global())
.cloned()
.peekable();
// If that is empty, we return the last import. (We need to chain because we need to return the same type for `impl Iterator`)
let extra = if iter.peek().is_some() {
None
} else {
self.node
.as_ref()
.and_then(|n| n.ImportSpecifier().last())
.and_then(|import| {
crate::typeloader::ImportedName::extract_imported_names(&import).last()
})
.and_then(|import| self.local_registry.lookup_element(&import.internal_name).ok())
.and_then(|c| match c {
ElementType::Component(c) => Some(c),
_ => None,
})
};
iter.chain(extra)
}
/// visit all root and used component (including globals)
pub fn visit_all_used_components(&self, mut v: impl FnMut(&Rc<Component>)) {
let used_types = self.used_types.borrow();
for c in &used_types.sub_components {
v(c);
}
v(&self.root_component);
for c in self.exported_roots() {
v(&c);
}
for c in &used_types.globals {
v(c);
}
@ -2394,7 +2404,6 @@ impl ExportedName {
pub struct Exports {
#[deref]
components_or_types: Vec<(ExportedName, Either<Rc<Component>, Type>)>,
last_exported_component: Option<Rc<Component>>,
}
impl Exports {
@ -2427,18 +2436,10 @@ impl Exports {
};
let mut sorted_exports_with_duplicates: Vec<(ExportedName, _)> = Vec::new();
let mut last_exported_component = None;
let mut extend_exports =
|it: &mut dyn Iterator<Item = (ExportedName, Either<Rc<Component>, Type>)>| {
for (name, compo_or_type) in it {
match compo_or_type.as_ref().left() {
Some(compo) if !compo.is_global() => {
last_exported_component = Some(compo.clone())
}
_ => {}
}
let pos = sorted_exports_with_duplicates
.partition_point(|(existing_name, _)| existing_name.name <= name.name);
sorted_exports_with_duplicates.insert(pos, (name, compo_or_type));
@ -2558,12 +2559,7 @@ impl Exports {
))
}
}
if last_exported_component.is_none() {
last_exported_component = inner_components.last().cloned();
}
Self { components_or_types: sorted_deduped_exports, last_exported_component }
Self { components_or_types: sorted_deduped_exports }
}
pub fn add_reexports(
@ -2612,13 +2608,7 @@ impl Exports {
})
.collect();
Self {
components_or_types,
last_exported_component: self
.last_exported_component
.as_ref()
.map(|lec| snapshotter.snapshot_component(lec)),
}
Self { components_or_types }
}
}

View file

@ -51,7 +51,6 @@ mod visible;
mod z_order;
use crate::expression_tree::Expression;
use crate::langtype::ElementType;
use crate::namedreference::NamedReference;
pub async fn run_passes(
@ -60,14 +59,6 @@ pub async fn run_passes(
keep_raw: bool,
diag: &mut crate::diagnostics::BuildDiagnostics,
) -> Option<crate::typeloader::TypeLoader> {
if matches!(
doc.root_component.root_element.borrow().base_type,
ElementType::Error | ElementType::Global
) {
// If there isn't a root component, we shouldn't do any of these passes
return None;
}
let style_metrics = {
// Ignore import errors
let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default();
@ -78,7 +69,6 @@ pub async fn run_passes(
};
let global_type_registry = type_loader.global_type_registry.clone();
let root_component = &doc.root_component;
run_import_passes(doc, type_loader, diag);
check_public_api::check_public_api(doc, diag);
@ -113,9 +103,10 @@ pub async fn run_passes(
inlining::inline(doc, inlining::InlineSelection::InlineOnlyRequiredComponents, diag);
collect_subcomponents::collect_subcomponents(doc);
focus_handling::call_focus_on_init(root_component);
ensure_window::ensure_window(root_component, &doc.local_registry, &style_metrics, diag);
for root_component in doc.exported_roots() {
focus_handling::call_focus_on_init(&root_component);
ensure_window::ensure_window(&root_component, &doc.local_registry, &style_metrics, diag);
}
doc.visit_all_used_components(|component| {
border_radius::handle_border_radius(component, diag);
@ -175,7 +166,9 @@ pub async fn run_passes(
}
materialize_fake_properties::materialize_fake_properties(component);
});
lower_layout::check_window_layout(root_component);
for root_component in doc.exported_roots() {
lower_layout::check_window_layout(&root_component);
}
collect_globals::collect_globals(doc, diag);
if type_loader.compiler_config.inline_all_elements {

View file

@ -9,7 +9,9 @@ use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel};
use crate::object_tree::{Component, Document, PropertyVisibility};
pub fn check_public_api(doc: &Document, diag: &mut BuildDiagnostics) {
check_public_api_component(&doc.root_component, diag);
for c in doc.exported_roots() {
check_public_api_component(&c, diag);
}
for (export_name, e) in &*doc.exports {
if let Some(c) = e.as_ref().left() {
if c.is_global() {

View file

@ -55,11 +55,13 @@ pub fn collect_custom_fonts<'a>(
Box::new(|font_path| Expression::StringLiteral(font_path.clone()))
};
doc.root_component.init_code.borrow_mut().font_registration_code.extend(
all_fonts.into_iter().map(|font_path| Expression::FunctionCall {
function: Box::new(registration_function.clone()),
arguments: vec![prepare_font_registration_argument(font_path)],
source_location: None,
}),
);
for c in doc.exported_roots() {
c.init_code.borrow_mut().font_registration_code.extend(all_fonts.iter().map(|font_path| {
Expression::FunctionCall {
function: Box::new(registration_function.clone()),
arguments: vec![prepare_font_registration_argument(font_path)],
source_location: None,
}
}));
}
}

View file

@ -24,7 +24,9 @@ pub fn collect_globals(doc: &Document, _diag: &mut BuildDiagnostics) {
}
}
}
collect_in_component(&doc.root_component, &mut set, &mut sorted_globals);
for component in doc.exported_roots() {
collect_in_component(&component, &mut set, &mut sorted_globals);
}
for component in &doc.used_types.borrow().sub_components {
collect_in_component(component, &mut set, &mut sorted_globals);
}

View file

@ -14,9 +14,9 @@ use std::rc::Rc;
pub fn collect_subcomponents(doc: &Document) {
let mut result = vec![];
let mut hash = HashSet::new();
collect_subcomponents_recursive(&doc.root_component, &mut result, &mut hash);
for component in doc.exported_roots() {
collect_subcomponents_recursive(&component, &mut result, &mut hash);
}
doc.used_types.borrow_mut().sub_components = result;
}

View file

@ -225,7 +225,9 @@ export component Foo {
assert!(!diag.has_error());
let out_binding = doc
.root_component
.inner_components
.last()
.unwrap()
.root_element
.borrow()
.bindings

View file

@ -43,7 +43,7 @@ pub fn embed_glyphs<'a>(
) {
use crate::diagnostics::Spanned;
let generic_diag_location = doc.root_component.root_element.borrow().to_source_location();
let generic_diag_location = doc.node.as_ref().map(|n| n.to_source_location());
characters_seen.extend(
('a'..='z')
@ -100,7 +100,7 @@ fn embed_glyphs_with_fontdb<'a>(
characters_seen: HashSet<char>,
all_docs: impl Iterator<Item = &'a crate::object_tree::Document> + 'a,
diag: &mut BuildDiagnostics,
generic_diag_location: crate::diagnostics::SourceLocation,
generic_diag_location: Option<crate::diagnostics::SourceLocation>,
) {
let fallback_fonts = get_fallback_fonts(fontdb);
@ -121,35 +121,25 @@ fn embed_glyphs_with_fontdb<'a>(
let default_font_ids = if !fontdb.default_font_family_ids.is_empty() {
fontdb.default_font_family_ids.clone()
} else {
let (family, source_location) = doc
.root_component
.root_element
.borrow()
.bindings
.get("default-font-family")
.and_then(|binding| match &binding.borrow().expression {
Expression::StringLiteral(family) => {
Some((Some(family.clone()), binding.borrow().span.clone()))
doc.exported_roots().filter_map(|c| {
c.root_element.borrow().bindings.get("default-font-family").and_then(|binding| {
match &binding.borrow().expression {
Expression::StringLiteral(family) => {
Some((Some(family.clone()), binding.borrow().span.clone()))
}
_ => None,
}
_ => None,
})
.unwrap_or_default();
match fontdb.query_with_family(Default::default(), family.as_deref()) {
Some(id) => vec![id],
None => {
}).filter_map(|(family, source_location)| {
fontdb.query_with_family(Default::default(), family.as_deref()).or_else(|| {
if let Some(source_location) = source_location {
diag.push_error_with_span("could not find font that provides specified family, falling back to Sans-Serif".to_string(), source_location);
} else {
diag.push_error(
"internal error: fontdb could not determine a default font for sans-serif"
.to_string(),
&generic_diag_location,
);
diag.push_error("internal error: fontdb could not determine a default font for sans-serif" .to_string(), &generic_diag_location);
};
return;
}
}
None
})
}).collect()
};
let default_font_paths = default_font_ids
@ -265,16 +255,16 @@ fn embed_glyphs_with_fontdb<'a>(
},
);
doc.root_component.init_code.borrow_mut().font_registration_code.push(
Expression::FunctionCall {
for c in doc.exported_roots() {
c.init_code.borrow_mut().font_registration_code.push(Expression::FunctionCall {
function: Box::new(Expression::BuiltinFunctionReference(
BuiltinFunction::RegisterBitmapFont,
None,
)),
arguments: vec![Expression::NumberLiteral(resource_id as _, Unit::None)],
source_location: None,
},
);
});
}
};
// Make sure to embed the default font first, because that becomes the default at run-time.

View file

@ -12,7 +12,7 @@ use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum InlineSelection {
InlineAllComponents,
InlineOnlyRequiredComponents,
@ -21,6 +21,7 @@ pub enum InlineSelection {
pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut BuildDiagnostics) {
fn inline_components_recursively(
component: &Rc<Component>,
roots: &HashSet<ByAddress<Rc<Component>>>,
inline_selection: InlineSelection,
diag: &mut BuildDiagnostics,
) {
@ -28,7 +29,7 @@ pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut Buil
let base = elem.borrow().base_type.clone();
if let ElementType::Component(c) = base {
// First, make sure that the component itself is properly inlined
inline_components_recursively(&c, inline_selection, diag);
inline_components_recursively(&c, roots, inline_selection, diag);
if c.parent_element.upgrade().is_some() {
// We should not inline a repeated element
@ -43,27 +44,30 @@ pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut Buil
|| element_require_inlining(elem)
// We always inline the root in case the element that instantiate this component needs full inlining
|| Rc::ptr_eq(elem, &component.root_element)
// We always inline other roots as a component can't be both a sub component and a root
|| roots.contains(&ByAddress(c.clone()))
}
} {
inline_element(elem, &c, component, diag);
}
}
});
component
.popup_windows
.borrow()
.iter()
.for_each(|p| inline_components_recursively(&p.component, inline_selection, diag))
component.popup_windows.borrow().iter().for_each(|p| {
inline_components_recursively(&p.component, roots, inline_selection, diag)
})
}
let mut roots = HashSet::new();
if inline_selection == InlineSelection::InlineOnlyRequiredComponents {
for component in doc.exported_roots() {
roots.insert(ByAddress(component.clone()));
}
}
for component in doc.exported_roots() {
inline_components_recursively(&component, &roots, inline_selection, diag);
let mut init_code = component.init_code.borrow_mut();
let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
init_code.constructor_code.splice(0..0, inlined_init_code.into_values());
}
inline_components_recursively(&doc.root_component, inline_selection, diag);
let mut init_code = doc.root_component.init_code.borrow_mut();
let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
init_code.constructor_code.splice(0..0, inlined_init_code.into_values());
}
fn clone_tuple<U: Clone, V: Clone>((u, v): (&U, &V)) -> (U, V) {
(u.clone(), v.clone())
}
fn element_key(e: ElementRc) -> ByAddress<ElementRc> {
@ -90,7 +94,11 @@ fn inline_element(
let priority_delta = 1 + elem_mut.inline_depth;
elem_mut.base_type = inlined_component.root_element.borrow().base_type.clone();
elem_mut.property_declarations.extend(
inlined_component.root_element.borrow().property_declarations.iter().map(clone_tuple),
inlined_component.root_element.borrow().property_declarations.iter().map(|(name, decl)| {
let mut decl = decl.clone();
decl.expose_in_public_api = false;
(name.clone(), decl)
}),
);
for (p, a) in inlined_component.root_element.borrow().property_analysis.borrow().iter() {

View file

@ -25,6 +25,16 @@ fn create_repeater_components(component: &Rc<Component>) {
let parent_element = Rc::downgrade(elem);
let mut elem = elem.borrow_mut();
if matches!(&elem.base_type, ElementType::Component(c) if c.parent_element.upgrade().is_some())
{
debug_assert!(std::rc::Weak::ptr_eq(
&parent_element,
&elem.base_type.as_component().parent_element
));
// Already processed (can happen if a component is both used and exported root)
return;
}
let comp = Rc::new(Component {
root_element: Rc::new(RefCell::new(Element {
id: elem.id.clone(),

View file

@ -12,7 +12,9 @@ use std::rc::Rc;
/// It currently does so by adding a number to the existing id
pub fn assign_unique_id(doc: &Document) {
let mut count = 0;
assign_unique_id_in_component(&doc.root_component, &mut count);
for component in doc.exported_roots() {
assign_unique_id_in_component(&component, &mut count);
}
for c in &doc.used_types.borrow().sub_components {
assign_unique_id_in_component(c, &mut count);
}

View file

@ -183,7 +183,6 @@ impl Snapshotter {
}
fn snapshot_document(&mut self, document: &object_tree::Document) -> object_tree::Document {
let root_component = self.snapshot_component(&document.root_component);
let inner_components =
document.inner_components.iter().map(|ic| self.snapshot_component(ic)).collect();
let exports = document.exports.snapshot(self);
@ -192,7 +191,6 @@ impl Snapshotter {
node: document.node.clone(),
inner_components,
inner_types: document.inner_types.clone(),
root_component,
local_registry: document.local_registry.snapshot(self),
custom_fonts: document.custom_fonts.clone(),
exports,

View file

@ -8,7 +8,7 @@ use core::ptr::NonNull;
use dynamic_type::{Instance, InstanceBox};
use i_slint_compiler::diagnostics::SourceFileVersion;
use i_slint_compiler::expression_tree::{Expression, NamedReference};
use i_slint_compiler::langtype::{ElementType, Type};
use i_slint_compiler::langtype::Type;
use i_slint_compiler::object_tree::ElementRc;
use i_slint_compiler::{diagnostics::BuildDiagnostics, object_tree::PropertyDeclaration};
use i_slint_compiler::{generator, object_tree, parser, CompilerConfiguration};
@ -844,17 +844,20 @@ pub async fn load(
#[allow(unused_mut)]
let mut it = {
let doc = loader.get_document(&path).unwrap();
if matches!(
doc.root_component.root_element.borrow().base_type,
ElementType::Global | ElementType::Error
) {
let root_component = doc
.exports
.iter()
.filter_map(|e| Some((&e.0.name_ident, e.1.as_ref().left()?)))
.max_by_key(|(n, _)| n.text_range().end())
.map(|(_, c)| c.clone());
let Some(root_component) = root_component.or_else(|| doc.exported_roots().last())
else {
diag.push_error_with_span("No component found".into(), Default::default());
return (Err(()), diag);
}
};
let compiled_globals = CompiledGlobalCollection::compile(&doc);
generate_item_tree(&doc.root_component, Some(compiled_globals), guard)
generate_item_tree(&root_component, Some(compiled_globals), guard)
};
#[cfg(feature = "highlight")]

View file

@ -0,0 +1,100 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
export global G {
in property <string> global-property: "Hello";
}
struct S { val: string }
component Shared {
in property <string> n;
out property <S> out: { val: G.global-property + " " + n };
for xx in 2 : Rectangle {}
}
/// This component is both exported and Used
export component Used {
in-out property <int> name;
for xx in 4 : Rectangle { }
}
export component FirstTest {
out property <string> global-prop: G.global-property;
out property <string> o: shared.out.val;
shared := Shared {
n: "Oli";
}
Used {}
// FIXME! as of now, only the last component's test property is evaluated by the test framework
out property <bool> test: false;
}
export component Z {
out property <bool> test: false;
}
export component SecondTest {
out property <string> global-prop: G.global-property;
out property <string> out: shared.out.val;
shared := Shared {
n: "Sim";
}
out property <bool> test: out == "Hello Sim";
}
/*
```rust
let instance1 = FirstTest::new().unwrap();
instance1.global::<G<'_>>().set_global_property("Hallo".into());
let instance2 = SecondTest::new().unwrap();
let instance3 = SecondTest::new().unwrap();
instance3.global::<G<'_>>().set_global_property("Bonjour".into());
assert_eq!(instance1.get_o(), "Hallo Oli");
assert_eq!(instance2.get_out(), "Hello Sim");
assert_eq!(instance3.get_out(), "Bonjour Sim");
```
```cpp
auto handle1 = FirstTest::create();
const FirstTest &instance1 = *handle1;
instance1.global<G>().set_global_property("Hallo");
auto handle2 = SecondTest::create();
const SecondTest &instance2 = *handle2;
auto handle3 = SecondTest::create();
const SecondTest &instance3 = *handle3;
instance3.global<G>().set_global_property("Bonjour");
assert_eq(instance1.get_o(), "Hallo Oli");
assert_eq(instance2.get_out(), "Hello Sim");
assert_eq(instance3.get_out(), "Bonjour Sim");
struct Shared {};
```
*/