Skip to content
Open
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 docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ The syntax tests are testing that the compiler show the right error messages in
The syntax tests are located in [internal/compiler/tests/syntax/](../internal/compiler/tests/syntax/) and it's driven by the
[`syntax_tests.rs`](../internal/compiler/tests/syntax_tests.rs) file. More info in the comments of that file.

In summary, each .slint files have comments with `^error` like so:
In summary, each .slint files have comments with `> <error` like so:

```ignore
foo bar
// ^error{parse error}
// > <error{parse error}
```

Meaning that there must be an error on the line above at the location pointed by the caret.
Meaning that there must be an error on the line above spanning `bar`, as indicated by the `>` and `<` arrows.

Ideally, each error message must be tested like so.

Expand Down
46 changes: 40 additions & 6 deletions internal/compiler/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use std::collections::BTreeSet;

/// Span represent an error location within a file.
///
/// Currently, it is just an offset in byte within the file.
/// Currently, it is just an offset in byte within the file + the corresponding length.
///
/// When the `proc_macro_span` feature is enabled, it may also hold a proc_macro span.
#[derive(Debug, Clone)]
pub struct Span {
pub offset: usize,
pub length: usize,
#[cfg(feature = "proc_macro_span")]
pub span: Option<proc_macro::Span>,
}
Expand All @@ -26,15 +27,16 @@ impl Span {
}

#[allow(clippy::needless_update)] // needed when `proc_macro_span` is enabled
pub fn new(offset: usize) -> Self {
Self { offset, ..Default::default() }
pub fn new(offset: usize, length: usize) -> Self {
Self { offset, length, ..Default::default() }
}
}

impl Default for Span {
fn default() -> Self {
Span {
offset: usize::MAX,
length: 0,
#[cfg(feature = "proc_macro_span")]
span: Default::default(),
}
Expand All @@ -43,7 +45,7 @@ impl Default for Span {

impl PartialEq for Span {
fn eq(&self, other: &Span) -> bool {
self.offset == other.offset
self.offset == other.offset && self.length == other.length
}
}

Expand Down Expand Up @@ -272,6 +274,36 @@ impl Diagnostic {
}
}

/// The exclusive end of this diagnostic.
/// Returns a tuple with the line (starting at 1) and column number (starting at 1)
///
/// Can also return (0, 0) if the span is invalid
pub fn end_line_column(&self) -> (usize, usize) {
if !self.span.span.is_valid() {
return (0, 0);
}
// The end_line_column is exclusive.
// Even if the span indicates a length of 0, the diagnostic should always
// return an end_line_column that is at least one offset further, so that at least one
// character is covered by the diagnostic.
let offset = self.span.span.offset + self.span.span.length.max(1);

match &self.span.source_file {
None => (0, 0),
Some(sl) => sl.line_column(offset),
}
}

/// Return the length of this diagnostic in characters.
pub fn length(&self) -> usize {
// Like in end_line_column, the length should always be 1, even if the span indicates a
// length of 0, as otherwise there is no character to display the diagnostic on.
self.span.span.length.max(1)
}

// NOTE: The return-type differs from the Spanned trait.
// Because this is public API (Diagnostic is re-exported by the Interpreter), we cannot change
// this.
/// return the path of the source file where this error is attached
pub fn source_file(&self) -> Option<&Path> {
self.span.source_file().map(|sf| sf.path())
Expand Down Expand Up @@ -404,8 +436,10 @@ impl BuildDiagnostics {
});
let file_span = file.span;
let s = codemap_diagnostic::SpanLabel {
span: file_span
.subspan(d.span.span.offset as u64, d.span.span.offset as u64),
span: file_span.subspan(
d.span.span.offset as u64,
(d.span.span.offset + d.span.span.length) as u64,
),
style: codemap_diagnostic::SpanStyle::Primary,
label: None,
};
Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ pub fn lex(mut source: &str) -> Vec<crate::parser::Token> {
kind: SyntaxKind::Whitespace,
text: source[..3].into(),
offset: 0,
length: 3,
..Default::default()
});
source = &source[3..];
Expand All @@ -215,6 +216,7 @@ pub fn lex(mut source: &str) -> Vec<crate::parser::Token> {
kind,
text: source[..len].into(),
offset,
length: len,
..Default::default()
});
offset += len;
Expand Down
12 changes: 8 additions & 4 deletions internal/compiler/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ pub struct Token {
pub kind: SyntaxKind,
pub text: SmolStr,
pub offset: usize,
pub length: usize,
#[cfg(feature = "proc_macro_span")]
pub span: Option<proc_macro::Span>,
}
Expand All @@ -473,6 +474,7 @@ impl Default for Token {
kind: SyntaxKind::Eof,
text: Default::default(),
offset: 0,
length: 0,
#[cfg(feature = "proc_macro_span")]
span: None,
}
Expand Down Expand Up @@ -692,7 +694,7 @@ impl Parser for DefaultParser<'_> {
fn error(&mut self, e: impl Into<String>) {
let current_token = self.current_token();
#[allow(unused_mut)]
let mut span = crate::diagnostics::Span::new(current_token.offset);
let mut span = crate::diagnostics::Span::new(current_token.offset, current_token.length);
#[cfg(feature = "proc_macro_span")]
{
span.span = current_token.span;
Expand All @@ -711,7 +713,7 @@ impl Parser for DefaultParser<'_> {
fn warning(&mut self, e: impl Into<String>) {
let current_token = self.current_token();
#[allow(unused_mut)]
let mut span = crate::diagnostics::Span::new(current_token.offset);
let mut span = crate::diagnostics::Span::new(current_token.offset, current_token.length);
#[cfg(feature = "proc_macro_span")]
{
span.span = current_token.span;
Expand Down Expand Up @@ -946,7 +948,8 @@ impl NodeOrToken {

impl Spanned for SyntaxNode {
fn span(&self) -> crate::diagnostics::Span {
crate::diagnostics::Span::new(self.node.text_range().start().into())
let range = self.node.text_range();
crate::diagnostics::Span::new(range.start().into(), range.len().into())
}

fn source_file(&self) -> Option<&SourceFile> {
Expand All @@ -966,7 +969,8 @@ impl Spanned for Option<SyntaxNode> {

impl Spanned for SyntaxToken {
fn span(&self) -> crate::diagnostics::Span {
crate::diagnostics::Span::new(self.token.text_range().start().into())
let range = self.token.text_range();
crate::diagnostics::Span::new(range.start().into(), range.len().into())
}

fn source_file(&self) -> Option<&SourceFile> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,41 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

Button1 := Rectangle {
// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
// ><warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
property <bool> cond;
accessible-role: cond ? button : AccessibleRole.text;
// ^error{The `accessible-role` property must be a constant expression}
// > <error{The `accessible-role` property must be a constant expression}

rr := Rectangle {
init => {
self.accessible-role = AccessibleRole.text;
// ^error{The property must be known at compile time and cannot be changed at runtime}
// > <error{The property must be known at compile time and cannot be changed at runtime}
}
}
}

Button2 := Rectangle {
// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
// ><warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
accessible-label: "the button";
// ^error{The `accessible-label` property can only be set in combination to `accessible-role`}
// > <error{The `accessible-label` property can only be set in combination to `accessible-role`}
}

Button3 := Rectangle {
// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
// ><warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
Rectangle {
accessible-role: text;
accessible-label: "the button";
}
}

export Test := Window {
// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
// ><warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}

Button1 { }
Button1 { accessible-description: "ok"; } // ok because Button1 has a role
Button2 { accessible-role: none; }
Button2 { }
Button3 {}
Button3 { accessible-description: "error";}
// ^error{The `accessible-description` property can only be set in combination to `accessible-role`}
// > <error{The `accessible-description` property can only be set in combination to `accessible-role`}
}
24 changes: 12 additions & 12 deletions internal/compiler/tests/syntax/analysis/binding_loop1.slint
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@


WithStates := Rectangle {
// ^warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
// ><warning{':=' to declare a component is deprecated. The new syntax declare components with 'component MyComponent {'. Read the documentation for more info}
property <brush> extra_background;
property <bool> condition;
background: yellow; //FIXME: ideally we'd keep the span within the state
// ^error{The binding for the property 'background' is part of a binding loop (extra-background -> background)}
// > <error{The binding for the property 'background' is part of a binding loop (extra-background -> background)}
states [
xxx when condition : {
background: extra_background;
Expand All @@ -16,32 +16,32 @@ WithStates := Rectangle {
}

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

property <int> a: 45 + root.b;
// ^error{The binding for the property 'a' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
// > <error{The binding for the property 'a' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
property <float> b: root.c;
// ^error{The binding for the property 'b' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
// > <error{The binding for the property 'b' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
property <int> c <=> d;
// ^error{The binding for the property 'c' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
// > <error{The binding for the property 'c' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
property <int> d: root.a + root.e;
// ^error{The binding for the property 'd' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
// > <error{The binding for the property 'd' is part of a binding loop (root_window.d -> root_window.c -> root_window.b -> root_window.a)}
property <int> e: root.b;
// ^error{The binding for the property 'e' is part of a binding loop (root_window.e -> root_window.d -> root_window.c -> root_window.b)}
// > <error{The binding for the property 'e' is part of a binding loop (root_window.e -> root_window.d -> root_window.c -> root_window.b)}
property <int> w: root.a + root.b; // This id not part of a loop

property<bool> cond: xx.x == 0;
// ^error{The binding for the property 'cond' is part of a binding loop (xx.y -> xx.x -> root_window.cond)}
// > <error{The binding for the property 'cond' is part of a binding loop (xx.y -> xx.x -> root_window.cond)}

xx := Rectangle {
x: y;
// ^error{The binding for the property 'x' is part of a binding loop (xx.y -> xx.x -> root_window.cond)}
// ><error{The binding for the property 'x' is part of a binding loop (xx.y -> xx.x -> root_window.cond)}
y: root.cond ? 42px : 55px;
// ^error{The binding for the property 'y' is part of a binding loop (xx.y -> xx.x -> root_window.cond)}
// > <error{The binding for the property 'y' is part of a binding loop (xx.y -> xx.x -> root_window.cond)}
}

WithStates {
extra_background: background;
// ^error{The binding for the property 'extra-background' is part of a binding loop (extra-background -> background)}
// > <error{The binding for the property 'extra-background' is part of a binding loop (extra-background -> background)}
}
}
Loading
Loading