Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only generate code for the exported Window #5475

Merged
merged 2 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions api/cpp/docs/generated_code.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

The Slint compiler [called by the build system](cmake_reference.md#slint_target_sources)
will generate a header file for the root `.slint` file.
This header file will contain a `class` with the same name as the root
component.

This class will have the following public member functions:
This header file will contain a `class` for every exported component from the main file that inherits from `Window` or `Dialog`.

These classes have the same name as the component will have the following public member functions:

* A `create` constructor function and a destructor.
* A `show` function, which will show the component on the screen.
Expand Down
7 changes: 3 additions & 4 deletions api/rs/slint/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,10 @@ cargo generate --git https://github.com/slint-ui/slint-rust-template

## Generated components

Currently, only the last component in a `.slint` source file is mapped to a Rust structure that be instantiated. We are tracking the
resolution of this limitation in <https://github.com/slint-ui/slint/issues/784>.
Exported component from the macro or the main file that inherit `Window` or `Dialog` is mapped to a Rust structure.

The component is generated and re-exported to the location of the [`include_modules!`] or [`slint!`] macro. It is represented
as a struct with the same name as the component.
The components are generated and re-exported to the location of the [`include_modules!`] or [`slint!`] macro.
It is represented as a struct with the same name as the component.

For example, if you have

Expand Down
2 changes: 1 addition & 1 deletion api/rs/slint/tests/simple_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn test_serialize_deserialize_struct() {
enum: TestEnum,
foo: int,
}
export component Test { }
export component Test inherits Window { }
}
let data = TestStruct { foo: 1, r#enum: TestEnum::World };
let serialized = serde_json::to_string(&data).unwrap();
Expand Down
25 changes: 18 additions & 7 deletions internal/compiler/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod typeregister;

pub mod passes;

use crate::generator::OutputFormat;
use std::path::Path;

/// Specify how the resources are embedded by the compiler
Expand Down Expand Up @@ -102,10 +103,16 @@ pub struct CompilerConfiguration {

/// Generate debug information for elements (ids, type names)
pub debug_info: bool,

/// When this is true, the passes will generate components for all exported Windows
/// and will throw a warning if an exported component is not a window.
/// If this is false, only the last component is exported, regardless if this is a Window or not,
/// (and it will be transformed in a Window)
pub generate_all_exported_windows: bool,
}

impl CompilerConfiguration {
pub fn new(output_format: crate::generator::OutputFormat) -> Self {
pub fn new(output_format: OutputFormat) -> Self {
let embed_resources = if std::env::var_os("SLINT_EMBED_TEXTURES").is_some()
|| std::env::var_os("DEP_MCU_BOARD_SUPPORT_MCU_EMBED_TEXTURES").is_some()
{
Expand All @@ -124,8 +131,8 @@ impl CompilerConfiguration {
} else {
match output_format {
#[cfg(feature = "rust")]
crate::generator::OutputFormat::Rust => EmbedResourcesKind::EmbedAllResources,
crate::generator::OutputFormat::Interpreter => EmbedResourcesKind::Nothing,
OutputFormat::Rust => EmbedResourcesKind::EmbedAllResources,
OutputFormat::Interpreter => EmbedResourcesKind::Nothing,
_ => EmbedResourcesKind::OnlyBuiltinResources,
}
};
Expand All @@ -137,7 +144,7 @@ impl CompilerConfiguration {
)
}),
// Currently, the interpreter needs the inlining to be on.
Err(_) => output_format == crate::generator::OutputFormat::Interpreter,
Err(_) => output_format == OutputFormat::Interpreter,
};

let scale_factor = std::env::var("SLINT_SCALE_FACTOR")
Expand All @@ -150,9 +157,12 @@ impl CompilerConfiguration {

let debug_info = std::env::var_os("SLINT_EMIT_DEBUG_INFO").is_some();

// The interpreter currently supports only generating the last component
let generate_all_exported_windows = output_format != OutputFormat::Interpreter;

let cpp_namespace = match output_format {
#[cfg(feature = "cpp")]
crate::generator::OutputFormat::Cpp(config) => match config.namespace {
OutputFormat::Cpp(config) => match config.namespace {
Some(namespace) => Some(namespace),
None => match std::env::var("SLINT_CPP_NAMESPACE") {
Ok(namespace) => Some(namespace),
Expand All @@ -176,6 +186,7 @@ impl CompilerConfiguration {
translation_domain: None,
cpp_namespace,
debug_info,
generate_all_exported_windows,
}
}
}
Expand Down Expand Up @@ -216,7 +227,7 @@ pub async fn compile_syntax_node(
let (foreign_imports, reexports) =
loader.load_dependencies_recursively(&doc_node, &mut diagnostics, &type_registry).await;

let doc = crate::object_tree::Document::from_node(
let mut doc = crate::object_tree::Document::from_node(
doc_node,
foreign_imports,
reexports,
Expand All @@ -225,7 +236,7 @@ pub async fn compile_syntax_node(
);

if !diagnostics.has_error() {
passes::run_passes(&doc, &mut loader, false, &mut diagnostics).await;
passes::run_passes(&mut doc, &mut loader, false, &mut diagnostics).await;
} else {
// Don't run all the passes in case of errors because because some invariants are not met.
passes::run_import_passes(&doc, &loader, &mut diagnostics);
Expand Down
17 changes: 17 additions & 0 deletions internal/compiler/object_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,16 @@ impl Document {
iter.chain(extra)
}

/// This is the component that is going to be instantiated by the interpreter
pub fn last_exported_component(&self) -> Option<Rc<Component>> {
self.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())
.or_else(|| self.exported_roots().last())
}

/// 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();
Expand Down Expand Up @@ -2592,6 +2602,13 @@ impl Exports {
.map(|index| self.components_or_types[index].1.clone())
}

pub fn retain(
&mut self,
func: impl FnMut(&mut (ExportedName, Either<Rc<Component>, Type>)) -> bool,
) {
self.components_or_types.retain_mut(func)
}

pub(crate) fn snapshot(&self, snapshotter: &mut crate::typeloader::Snapshotter) -> Self {
let components_or_types = self
.components_or_types
Expand Down
8 changes: 4 additions & 4 deletions internal/compiler/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use crate::expression_tree::Expression;
use crate::namedreference::NamedReference;

pub async fn run_passes(
doc: &crate::object_tree::Document,
doc: &mut crate::object_tree::Document,
type_loader: &mut crate::typeloader::TypeLoader,
keep_raw: bool,
diag: &mut crate::diagnostics::BuildDiagnostics,
Expand All @@ -70,7 +70,7 @@ pub async fn run_passes(

let global_type_registry = type_loader.global_type_registry.clone();
run_import_passes(doc, type_loader, diag);
check_public_api::check_public_api(doc, diag);
check_public_api::check_public_api(doc, &type_loader.compiler_config, diag);

let raw_type_loader =
keep_raw.then(|| crate::typeloader::snapshot_with_extra_doc(type_loader, doc).unwrap());
Expand Down Expand Up @@ -241,15 +241,15 @@ pub async fn run_passes(
type_loader.compiler_config.scale_factor,
font_pixel_sizes,
characters_seen,
std::iter::once(doc).chain(type_loader.all_documents()),
std::iter::once(&*doc).chain(type_loader.all_documents()),
diag,
);
}
_ => {
// Create font registration calls for custom fonts, unless we're embedding pre-rendered glyphs
collect_custom_fonts::collect_custom_fonts(
doc,
std::iter::once(doc).chain(type_loader.all_documents()),
std::iter::once(&*doc).chain(type_loader.all_documents()),
type_loader.compiler_config.embed_resources
== crate::EmbedResourcesKind::EmbedAllResources,
);
Expand Down
37 changes: 36 additions & 1 deletion internal/compiler/passes/check_public_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,43 @@ use std::rc::Rc;

use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel};
use crate::object_tree::{Component, Document, PropertyVisibility};
use crate::CompilerConfiguration;
use itertools::Either;

pub fn check_public_api(
doc: &mut Document,
config: &CompilerConfiguration,
diag: &mut BuildDiagnostics,
) {
let last = doc.last_exported_component();

if config.generate_all_exported_windows {
doc.exports.retain(|export| {
// Warn about exported non-window (and remove them from the export unless it's the last for compatibility)
if let Either::Left(c) = &export.1 {
if !c.is_global() && !super::ensure_window::inherits_window(c) {
let is_last = last.as_ref().is_some_and(|last| !Rc::ptr_eq(last, c));
if is_last {
diag.push_warning(format!("Exported component '{}' doesn't inherit Window. No code will be generated for it", export.0.name), &export.0.name_ident);
return false;
} else {
diag.push_warning(format!("Exported component '{}' doesn't inherit Window. This is deprecated", export.0.name), &export.0.name_ident);
}
}
}
true
});
} else {
// Only keep the last component if there is one
doc.exports.retain(|export| {
if let Either::Left(c) = &export.1 {
c.is_global() || last.as_ref().map_or(true, |last| Rc::ptr_eq(last, c))
} else {
true
}
});
}

pub fn check_public_api(doc: &Document, diag: &mut BuildDiagnostics) {
for c in doc.exported_roots() {
check_public_api_component(&c, diag);
}
Expand Down
10 changes: 7 additions & 3 deletions internal/compiler/passes/ensure_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ pub fn ensure_window(
);
}

if component.root_element.borrow().builtin_type().map_or(true, |b| {
matches!(b.name.as_str(), "Window" | "Dialog" | "WindowItem" | "PopupWindow")
}) {
if inherits_window(component) {
return; // already a window, nothing to do
}

Expand Down Expand Up @@ -145,3 +143,9 @@ pub fn ensure_window(
}
});
}

pub fn inherits_window(component: &Rc<Component>) -> bool {
component.root_element.borrow().builtin_type().map_or(true, |b| {
matches!(b.name.as_str(), "Window" | "Dialog" | "WindowItem" | "PopupWindow")
})
}
24 changes: 24 additions & 0 deletions internal/compiler/tests/syntax/exports/export_non_window.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright © SixtyFPS GmbH <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

//config:generate_all_exported_windows

component Foo {}

component Bar inherits Window {}

export component Abc inherits Bar {}

export component Cde inherits Rectangle {}
// ^warning{Exported component 'Cde' doesn't inherit Window. No code will be generated for it}

export component Fgh inherits Foo {}
// ^warning{Exported component 'Fgh' doesn't inherit Window. No code will be generated for it}

export component Ijk inherits Dialog { Rectangle {} }

export component Xyz {}
// ^warning{Exported component 'Xyz' doesn't inherit Window. No code will be generated for it}

export component Last {}
// ^warning{Exported component 'Last' doesn't inherit Window. This is deprecated}
2 changes: 2 additions & 0 deletions internal/compiler/tests/syntax_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ fn process_file_source(
compiler_config.embed_resources = i_slint_compiler::EmbedResourcesKind::OnlyBuiltinResources;
compiler_config.enable_experimental = true;
compiler_config.style = Some("fluent".into());
compiler_config.generate_all_exported_windows =
source.contains("config:generate_all_exported_windows");
let compile_diagnostics = if !parse_diagnostics.has_error() {
let (_, build_diags, _) = spin_on::spin_on(i_slint_compiler::compile_syntax_node(
syntax_node.clone(),
Expand Down
4 changes: 2 additions & 2 deletions internal/compiler/typeloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ impl TypeLoader {
) -> (PathBuf, Option<TypeLoader>) {
let path = crate::pathutils::clean_path(path);
let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
let (path, doc) = Self::load_file_no_pass(
let (path, mut doc) = Self::load_file_no_pass(
&state,
&path,
version,
Expand All @@ -1077,7 +1077,7 @@ impl TypeLoader {
let mut state = state.borrow_mut();
let state = &mut *state;
let raw_type_loader = if !state.diag.has_error() {
crate::passes::run_passes(&doc, state.tl, keep_raw, state.diag).await
crate::passes::run_passes(&mut doc, state.tl, keep_raw, state.diag).await
} else {
None
};
Expand Down
9 changes: 1 addition & 8 deletions internal/interpreter/dynamic_item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -844,14 +844,7 @@ pub async fn load(
#[allow(unused_mut)]
let mut it = {
let doc = loader.get_document(&path).unwrap();
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 {
let Some(root_component) = doc.last_exported_component() else {
diag.push_error_with_span("No component found".into(), Default::default());
return (Err(()), diag);
};
Expand Down
19 changes: 14 additions & 5 deletions tests/cases/exports/multiple_components.slint
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ component Shared {
for xx in 2 : Rectangle {}
}

/// This component is both exported and Used
export component Used {
// This component is both exported and Used
export component Used inherits Window {
in-out property <int> name;
for xx in 4 : Rectangle { }
}

export component FirstTest {
export component FirstTest inherits Window {

out property <string> global-prop: G.global-property;
out property <string> o: shared.out.val;
Expand All @@ -33,13 +33,15 @@ export component FirstTest {
out property <bool> test: false;
}

export component Z {
export component Z inherits Window {
out property <bool> test: false;
}

export component NotAWindow {}


export component SecondTest {

export component SecondTest inherits Window {
out property <string> global-prop: G.global-property;
out property <string> out: shared.out.val;

Expand Down Expand Up @@ -69,6 +71,12 @@ 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");

#[allow(unused)]
pub struct Shared;
#[allow(unused)]
pub struct NotAWindow;

```

```cpp
Expand All @@ -89,6 +97,7 @@ assert_eq(instance2.get_out(), "Hello Sim");
assert_eq(instance3.get_out(), "Bonjour Sim");

struct Shared {};
struct NotAWindow {};
```


Expand Down
Loading