Skip to content

Add local variables, closes #2752 #8740

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

Merged
merged 27 commits into from
Jun 26, 2025

Conversation

codeshaunted
Copy link
Contributor

Adds support for immutable local variables in .slint functions using a new let keyword with an optional type annotation:

let foo: int = 1; // with type annotation
let bar = 2; // without type annotation

Shadowing is allowed:

let foo = 1;
let foo = foo + 1;

debug(foo); // prints "2"

let foo = "hello world";

debug(foo); // prints "hello world"

Internally an extra discriminator field was added to the StoreLocalVariable and ReadLocalVariable expressions, so that shadowing support could be added in the C++ generator, as C++ does not support variable shadowing.

@codeshaunted codeshaunted force-pushed the avery/local-variables branch from 5ef96a0 to 19c20ea Compare June 20, 2025 21:21
Copy link
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is great!

Just added some comment inline, in particular, i think the "discriminator" should be part of the name in the Expression

Another thing is that the discriminator should probably be global, because otherwise we might have conflicts with inlining.

There should also be tests for error in internal/compiler/tests/syntax. For example that test that the types are respected and that it is error otherwise.

let foo : int = "1" should be an error. `property xyz : { let foo : string = 1; return foo; } should be an error, and so on.

Shadowing is allowed:

I know i said in the chat it should be allowed. But I'm thinking we shouldn't allow this at first in Slint. Make it an error for now and we can still allow it later if we think this is useful, but it is not used in other programming languages, so i wonder if we should really do that.

Anyway, great patch, thanks for contributing.

@@ -39,6 +39,37 @@ Functions can be annotated with the `pure` keyword.
This indicates that the function does not cause any side effects.
More details can be found in the <Link type="Purity" /> chapter.

## Local Variables
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be documented in the expressions-and-statements section, because it is basically available everywhere, not only in functions.

@@ -611,13 +611,15 @@ pub enum Expression {
/// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable
StoreLocalVariable {
name: SmolStr,
discriminator: Option<usize>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd rather the discriminator be part of the name.
The name should probably be prefixed as well so that it doesn't conflict with internal variable. Eg, if you have let foo = ..., the name should be local_foo_1

@@ -50,6 +55,11 @@ pub fn parse_statement(p: &mut impl Parser) -> bool {
return true;
}

if p.peek().as_str() == "let" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we have the same problem with return, but we should check that the next token is also an identifier. So if there is a property called let, code like let = 3; or let + 1 still works.

if (true) {
let a = a + 1;

return a;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we also have a version of the test without the return here? (to make sure that the original value is not changed.

@codeshaunted codeshaunted force-pushed the avery/local-variables branch from 34e4a19 to 84fd545 Compare June 24, 2025 21:36
@codeshaunted codeshaunted marked this pull request as draft June 24, 2025 21:46
Copy link
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again. This looks good to me.
Please just cleanup the patch a bit for the unnecessary changes.

I see that the C++ test is failing with a warning about unused variable. To solve this we could either:

  • add [[maybe_unused]] to the variable declaration. (Although this warning is valid, it shows in the generated code instead of the Slint code)
  • ignore this particular warning for this particular test somehow.

@@ -2516,7 +2516,7 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
}
}

Expression::StoreLocalVariable { name, value } => {
Expression::StoreLocalVariable { name, value, .. } => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are no more extra discriminant, the patch could be a bit cleaned up for these changes.

@@ -3132,7 +3132,7 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
Expression::StoreLocalVariable { name, value } => {
format!("auto {} = {};", ident(name), compile_expression(value, ctx))
}
Expression::ReadLocalVariable { name, .. } => ident(name).to_string(),
Expression::ReadLocalVariable { name, .. } => format!("{}", ident(name)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be reverted. I think clippy actually warns about use of format when to_string can be used

.push_error(format!("Redeclaration of local variables is not allowed"), &node);
return Expression::Invalid;
}
// conflicts with something else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imho we should be careful because this means that adding anything in Skint becomes a breaking change.

Imagine someone writes

let Foo = 42;

But now if I'm adding an enum called Foo in Slint, this code would break.

So I think we should only reject if it overrides a local variables (including the function parameters) but not a global one.

// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

export X := Rectangle {
// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use the new syntax in new tests.

// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
background: {
let blue: brush = 42;
// ^error{Local variable declaration conflicts with existing name}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment why I think this should actually be accepted.

@codeshaunted codeshaunted force-pushed the avery/local-variables branch from 436ab75 to d1993da Compare June 25, 2025 19:50
@codeshaunted codeshaunted marked this pull request as ready for review June 25, 2025 21:36
Copy link
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.
Looks good to me.
I'll merge soon.

@ogoffart ogoffart merged commit d2a5ae0 into slint-ui:master Jun 26, 2025
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants