Skip to main content

i_slint_compiler/
lib.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore inlines namedreference pathutils
5#![doc = include_str!("README.md")]
6#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8// It would be nice to keep the compiler free of unsafe code
9#![deny(unsafe_code)]
10
11#[cfg(feature = "proc_macro_span")]
12extern crate proc_macro;
13
14use core::future::Future;
15use core::pin::Pin;
16use std::cell::RefCell;
17use std::collections::HashMap;
18use std::rc::Rc;
19
20mod builtin_elements;
21pub mod builtin_macros;
22pub mod data_uri;
23pub mod diagnostics;
24pub mod embedded_resources;
25pub mod expression_tree;
26pub mod fileaccess;
27pub mod generator;
28pub mod langtype;
29pub mod layout;
30pub mod lexer;
31pub mod literals;
32pub mod llr;
33pub mod lookup;
34pub mod namedreference;
35pub mod object_tree;
36pub mod parser;
37pub mod pathutils;
38pub mod symbol_counters;
39#[cfg(feature = "bundle-translations")]
40pub mod translations;
41pub mod typeloader;
42pub mod typeregister;
43
44pub mod passes;
45
46use crate::generator::OutputFormat;
47use std::path::Path;
48
49/// Specify how the resources are embedded by the compiler
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum EmbedResourcesKind {
52    /// Embeds nothing (only useful for interpreter)
53    Nothing,
54    /// Only embed builtin resources (such as widget assets shipped with Slint).
55    ///
56    /// User resources are loaded from their absolute path at run-time.
57    OnlyBuiltinResources,
58    /// Don't embed resources, but list them in the Document as if they were embedded.
59    ///
60    /// Used by tools such as the LSP that need to know about all resources without embedding them.
61    ListAllResources,
62    /// Embed the content of all image resources in the binary as-is (a compressed PNG stays
63    /// compressed), to be decoded at run-time.
64    EmbedAllResources,
65    #[cfg(feature = "renderer-software")]
66    /// Pre-process images and fonts at compile time and embed them as uncompressed pixel data,
67    /// ready to be drawn by the software renderer without any decoding at run-time.
68    ///
69    /// Useful for MCUs with no file system and little RAM.
70    /// Only the Slint software renderer can use these resources; Skia and FemtoVG can't.
71    EmbedTextures,
72}
73
74/// This enum specifies the default translation context when no context is explicitly
75/// specified in the `@tr("context" => ...)` macro.
76#[derive(Clone, Debug, Eq, PartialEq)]
77#[non_exhaustive]
78pub enum DefaultTranslationContext {
79    /// The default translation context is the component name in which the `@tr` is written.
80    ///
81    /// This is the default behavior of `slint-tr-extractor`.
82    ComponentName,
83    /// Opt out of the default translation context.
84    ///
85    /// When using this option, invoke `slint-tr-extractor` with `--no-default-translation-context`
86    /// to make sure that the translation files have no context for strings which didn't specify a context.
87    None,
88}
89
90#[derive(Clone, Debug, Eq, PartialEq, Default)]
91#[non_exhaustive]
92pub enum ComponentSelection {
93    /// All components that inherit from Window.
94    ///
95    /// Note: Components marked for export but lacking Window inheritance are not selected (this will produce a warning),
96    /// For compatibility reason, the last exported component is still selected even if it doesn't inherit Window,
97    /// and if no component is exported, the last component is selected
98    #[default]
99    ExportedWindows,
100
101    /// The Last component (legacy for the viewer / interpreter)
102    ///
103    /// Only the last exported component is generated, regardless if this is a Window or not,
104    /// (and it will be transformed in a Window)
105    LastExported,
106
107    /// The component with the given name is generated
108    Named(String),
109}
110
111/// Type alias for the callback to open files mentioned in `import` statements
112///
113/// This is a dyn-compatible version of:
114///
115/// ```ignore
116/// async fn(String) -> Option<std::io::Result<String>>
117/// ```
118///
119/// Unfortunately AsyncFn is not dyn-compatible yet.
120pub type OpenImportCallback =
121    Rc<dyn Fn(String) -> Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>>;
122pub type ResourceUrlMapper =
123    Rc<dyn Fn(&url::Url) -> Pin<Box<dyn Future<Output = Option<url::Url>>>>>;
124
125/// CompilationConfiguration allows configuring different aspects of the compiler.
126#[derive(Clone)]
127pub struct CompilerConfiguration {
128    /// Indicate whether to embed resources such as images in the generated output or whether
129    /// to retain references to the resources on the file system.
130    pub embed_resources: EmbedResourcesKind,
131    /// Whether to use SDF when pre-rendering fonts.
132    #[cfg(all(feature = "renderer-software", feature = "sdf-fonts"))]
133    pub use_sdf_fonts: bool,
134    /// The compiler will look in these paths for components used in the file to compile.
135    pub include_paths: Vec<std::path::PathBuf>,
136    /// The compiler will look in these paths for library imports.
137    pub library_paths: HashMap<String, std::path::PathBuf>,
138    /// the name of the style. (eg: "native")
139    pub style: Option<String>,
140
141    /// Callback to load import files
142    ///
143    /// The callback should open the file specified by the given file name and
144    /// return a future that provides the text content of the file as output.
145    pub open_import_callback: Option<OpenImportCallback>,
146    /// Callback to map URLs for resources
147    ///
148    /// The function takes the url and returns the mapped URL (or None if not mapped)
149    pub resource_url_mapper: Option<ResourceUrlMapper>,
150
151    /// Run the pass that inlines all the elements.
152    ///
153    /// This may help optimization to optimize the runtime resources usages,
154    /// but at the cost of much more generated code and binary size.
155    pub inline_all_elements: bool,
156
157    /// Compile time scale factor to apply to embedded resources such as images and glyphs.
158    /// It will also be set as a const scale factor on the `slint::Window`.
159    pub const_scale_factor: Option<f32>,
160
161    /// Whether image sizes are known when a compiled component is instantiated.
162    /// This is false when the generated code may run on the web, where the browser
163    /// decodes images asynchronously and the size updates once an image is loaded,
164    /// so that expressions using an image size stay in bindings.
165    pub const_image_sizes: bool,
166
167    /// expose the accessible role and properties
168    pub accessibility: bool,
169
170    /// Add support for experimental features
171    pub enable_experimental: bool,
172
173    /// The domain used as one of the parameter to the translate function
174    pub translation_domain: Option<String>,
175    /// When Some, this is the path where the translations are looked at to bundle the translations
176    #[cfg(feature = "bundle-translations")]
177    pub translation_path_bundle: Option<std::path::PathBuf>,
178    /// Default translation context
179    pub default_translation_context: DefaultTranslationContext,
180
181    /// Do not generate the hook to create native menus
182    pub no_native_menu: bool,
183
184    /// C++ namespace
185    pub cpp_namespace: Option<String>,
186
187    /// When true, fail the build when a binding loop is detected with a window layout property
188    /// (otherwise this is a compatibility warning)
189    pub error_on_binding_loop_with_window_layout: bool,
190
191    /// Generate debug information for elements (ids, type names)
192    pub debug_info: bool,
193
194    /// Write, next to the generated code, the map of its coverage points of
195    /// the `.slint` source, for `slint-sc-coverage`. Only the Slint SC
196    /// generator honors it, and only when writing to a file.
197    pub coverage: bool,
198
199    /// Generate debug hooks to inspect/override properties.
200    pub debug_hooks: Option<std::hash::RandomState>,
201
202    pub components_to_generate: ComponentSelection,
203
204    /// The name of the library when compiling as a library.
205    pub library_name: Option<String>,
206
207    /// Specify the Rust module to place the generated code in.
208    pub rust_module: Option<String>,
209
210    /// Set automatically when the output format is `SlintSc`.
211    /// The compiler rejects all features not supported by the
212    /// safety-critical subset.
213    #[cfg(feature = "slint-sc")]
214    pub(crate) slint_sc: bool,
215
216    /// Set by tools such as `slint-viewer`, the LSP (editor diagnostics/preview), and the
217    /// live-reload runtime to indicate that the `.slint` file is being previewed rather than
218    /// driven by real host application logic.
219    pub is_preview: bool,
220}
221
222impl CompilerConfiguration {
223    pub fn new(output_format: OutputFormat) -> Self {
224        let embed_resources = if std::env::var_os("SLINT_EMBED_TEXTURES").is_some()
225            || std::env::var_os("DEP_MCU_BOARD_SUPPORT_MCU_EMBED_TEXTURES").is_some()
226        {
227            #[cfg(not(feature = "renderer-software"))]
228            panic!(
229                "the renderer-software feature must be enabled in i-slint-compiler when embedding textures"
230            );
231            #[cfg(feature = "renderer-software")]
232            EmbedResourcesKind::EmbedTextures
233        } else if let Ok(var) = std::env::var("SLINT_EMBED_RESOURCES") {
234            let var = var.parse::<bool>().unwrap_or_else(|_|{
235                panic!("SLINT_EMBED_RESOURCES has incorrect value. Must be either unset, 'true' or 'false'")
236            });
237            match var {
238                true => EmbedResourcesKind::EmbedAllResources,
239                false => EmbedResourcesKind::OnlyBuiltinResources,
240            }
241        } else {
242            match output_format {
243                #[cfg(feature = "rust")]
244                OutputFormat::Rust => EmbedResourcesKind::EmbedAllResources,
245                OutputFormat::Interpreter => EmbedResourcesKind::Nothing,
246                _ => EmbedResourcesKind::OnlyBuiltinResources,
247            }
248        };
249
250        let inline_all_elements = match std::env::var("SLINT_INLINING") {
251            Ok(var) => var.parse::<bool>().unwrap_or_else(|_| {
252                panic!(
253                    "SLINT_INLINING has incorrect value. Must be either unset, 'true' or 'false'"
254                )
255            }),
256            // Currently, the interpreter needs the inlining to be on.
257            Err(_) => output_format == OutputFormat::Interpreter,
258        };
259
260        // The Slint SC generator flattens the exported component's element
261        // tree, so user-defined components must be inlined away. This
262        // overrides a SLINT_INLINING=false env override.
263        #[cfg(feature = "slint-sc")]
264        let inline_all_elements =
265            inline_all_elements || matches!(output_format, OutputFormat::SlintSc);
266
267        let const_scale_factor = std::env::var("SLINT_SCALE_FACTOR")
268            .ok()
269            .and_then(|x| x.parse::<f32>().ok())
270            .filter(|f| *f > 0.);
271
272        let const_image_sizes = match std::env::var("CARGO_CFG_TARGET_FAMILY") {
273            // Set by cargo when running in a build script (slint-build): the target is known.
274            Ok(target_family) => !target_family.split(',').any(|f| f == "wasm"),
275            // The target is unknown (slint! macro, C++). The interpreter compiles for the
276            // architecture it runs on; otherwise assume the code may run on the web.
277            Err(_) => output_format == OutputFormat::Interpreter && !cfg!(target_family = "wasm"),
278        };
279
280        let enable_experimental = std::env::var_os("SLINT_ENABLE_EXPERIMENTAL_FEATURES").is_some();
281
282        let debug_info = std::env::var_os("SLINT_EMIT_DEBUG_INFO").is_some();
283
284        #[cfg(feature = "slint-sc")]
285        let slint_sc = matches!(output_format, OutputFormat::SlintSc);
286
287        let cpp_namespace = match output_format {
288            #[cfg(feature = "cpp")]
289            OutputFormat::Cpp(config) => match config.namespace {
290                Some(namespace) => Some(namespace),
291                None => std::env::var("SLINT_CPP_NAMESPACE").ok(),
292            },
293            _ => None,
294        };
295
296        let style = std::env::var("SLINT_STYLE").ok();
297
298        Self {
299            embed_resources,
300            include_paths: Default::default(),
301            library_paths: Default::default(),
302            style,
303            open_import_callback: None,
304            resource_url_mapper: None,
305            inline_all_elements,
306            const_scale_factor,
307            const_image_sizes,
308            accessibility: true,
309            enable_experimental,
310            translation_domain: None,
311            default_translation_context: DefaultTranslationContext::ComponentName,
312            no_native_menu: false,
313            cpp_namespace,
314            error_on_binding_loop_with_window_layout: false,
315            debug_info,
316            coverage: false,
317            debug_hooks: None,
318            components_to_generate: ComponentSelection::ExportedWindows,
319            #[cfg(all(feature = "renderer-software", feature = "sdf-fonts"))]
320            use_sdf_fonts: false,
321            #[cfg(feature = "bundle-translations")]
322            translation_path_bundle: std::env::var("SLINT_BUNDLE_TRANSLATIONS")
323                .ok()
324                .map(|x| x.into()),
325            library_name: None,
326            rust_module: None,
327            #[cfg(feature = "slint-sc")]
328            slint_sc,
329            is_preview: false,
330        }
331    }
332}
333
334/// Prepare for compilation of the source file
335/// - storing parser configuration
336/// - setting up the parser
337fn prepare_for_compile(
338    diagnostics: &mut diagnostics::BuildDiagnostics,
339    #[allow(unused_mut)] mut compiler_config: CompilerConfiguration,
340) -> typeloader::TypeLoader {
341    #[cfg(feature = "renderer-software")]
342    if compiler_config.embed_resources == EmbedResourcesKind::EmbedTextures {
343        // HACK: disable accessibility when compiling for the software renderer
344        // accessibility is not supported with backend that support software renderer anyway
345        compiler_config.accessibility = false;
346    }
347
348    diagnostics.enable_experimental = compiler_config.enable_experimental;
349    #[cfg(feature = "slint-sc")]
350    {
351        diagnostics.slint_sc = compiler_config.slint_sc;
352    }
353
354    typeloader::TypeLoader::new(compiler_config, diagnostics)
355}
356
357pub async fn compile_syntax_node(
358    doc_node: parser::SyntaxNode,
359    mut diagnostics: diagnostics::BuildDiagnostics,
360    #[allow(unused_mut)] mut compiler_config: CompilerConfiguration,
361) -> (object_tree::Document, diagnostics::BuildDiagnostics, typeloader::TypeLoader) {
362    let mut loader = prepare_for_compile(&mut diagnostics, compiler_config);
363
364    let doc_node: parser::syntax_nodes::Document = doc_node.into();
365
366    let type_registry =
367        Rc::new(RefCell::new(typeregister::TypeRegister::new(&loader.global_type_registry)));
368    let (foreign_imports, reexports) =
369        loader.load_dependencies_recursively(&doc_node, &mut diagnostics, &type_registry).await;
370
371    let ignore_missing_font_files = loader.compiler_config.resource_url_mapper.is_some();
372    let mut doc = crate::object_tree::Document::from_node(
373        doc_node,
374        foreign_imports,
375        reexports,
376        &mut diagnostics,
377        &type_registry,
378        ignore_missing_font_files,
379        &loader.symbol_counters,
380    );
381
382    if !diagnostics.has_errors() {
383        passes::run_passes(&mut doc, &mut loader, false, &mut diagnostics).await;
384    } else {
385        // Don't run all the passes in case of errors because because some invariants are not met.
386        passes::run_import_passes(&doc, &loader, &mut diagnostics);
387    }
388    (doc, diagnostics, loader)
389}
390
391/// Pass a file to the compiler and process it fully, applying all the
392/// necessary compilation passes.
393///
394/// This returns a `Tuple` containing the actual cleaned `path` to the file,
395/// a set of `BuildDiagnostics` and a `TypeLoader` with all compilation passes applied.
396pub async fn load_root_file(
397    path: &Path,
398    source_path: &Path,
399    source_code: String,
400    mut diagnostics: diagnostics::BuildDiagnostics,
401    #[allow(unused_mut)] mut compiler_config: CompilerConfiguration,
402) -> (std::path::PathBuf, diagnostics::BuildDiagnostics, typeloader::TypeLoader) {
403    let mut loader = prepare_for_compile(&mut diagnostics, compiler_config);
404
405    let (path, _) =
406        loader.load_root_file(path, source_path, source_code, false, &mut diagnostics).await;
407
408    (path, diagnostics, loader)
409}
410
411/// Pass a file to the compiler and process it fully, applying all the
412/// necessary compilation passes, just like `load_root_file`.
413///
414/// This returns a `Tuple` containing the actual cleaned `path` to the file,
415/// a set of `BuildDiagnostics`, a `TypeLoader` with all compilation passes
416/// applied and another `TypeLoader` with a minimal set of passes applied to it.
417pub async fn load_root_file_with_raw_type_loader(
418    path: &Path,
419    source_path: &Path,
420    source_code: String,
421    mut diagnostics: diagnostics::BuildDiagnostics,
422    #[allow(unused_mut)] mut compiler_config: CompilerConfiguration,
423) -> (
424    std::path::PathBuf,
425    diagnostics::BuildDiagnostics,
426    typeloader::TypeLoader,
427    Option<typeloader::TypeLoader>,
428) {
429    let mut loader = prepare_for_compile(&mut diagnostics, compiler_config);
430
431    let (path, raw_type_loader) =
432        loader.load_root_file(path, source_path, source_code, true, &mut diagnostics).await;
433
434    (path, diagnostics, loader, raw_type_loader)
435}
436
437/// Returns true and emits an error if experimental features should be disabled.
438///
439/// Some experimental features are used internally which is why this function also checks
440/// `TypeRegister::expose_internal_types`.
441fn reject_experimental_feature(
442    diagnostics: &mut diagnostics::BuildDiagnostics,
443    type_register: &typeregister::TypeRegister,
444    feature: &str,
445    source: &dyn diagnostics::Spanned,
446) -> bool {
447    if !diagnostics.enable_experimental && !type_register.expose_internal_types {
448        diagnostics.push_error(format!("'{feature}' is an experimental feature"), source);
449        true
450    } else {
451        false
452    }
453}