Slint にバグ報告をしてみました

# はじめに

この記事は [Slint Advent Calendar 2024](https://qiita.com/advent-calendar/2024/slint) 20日目の記事です。

昨日は @hermit4 さんによる [祝 Slint 1.9リリース](https://qiita.com/hermit4/items/2ebbef1b484352a7d177) でした。

 [.slint のドキュメント](https://docs.slint.dev/latest/docs/slint/) が大幅に改善されてとてもうれしいです。

実は今年のアドベントカレンダーを書いたりネタを探すのに、[リリース前のドキュメント](https://snapshots.slint.dev/master/docs/slint/) の方もよく見ていたので、途中から薄々感じていたのですが、３日目に書いた [.slint のドキュメントの構成をまとめました](https://qiita.com/task_jp/items/a1ded3ce10d5018e7a76) が台無しになりちょっとだけ悲しいです。


# きっかけ

2日前に書いた [Slint のダイアルのサンプルコードから学ぶ](https://qiita.com/task_jp/items/34949faa225c17d197e6) で Slint のダイアルのサンプルコードを紹介しました。
その TODO にあった `Lock the dial so it cannot be rotated between the blank start and end angles.` に挑戦しようと思って少しコードを変更していたところ、思ったように動かなかったので調査をしていました。

## 挙動が変だったコード

そのデバッグで色々なコードを試していたのですが、 [Types](https://docs.slint.dev/1.8.0/docs/slint/src/language/syntax/types) で定義されている型の１つである、`angle` に対して、[Math](https://docs.slint.dev/latest/docs/slint/src/language/builtins/namespaces#math) にある `abs()` を利用したところ、思ったように動きませんでした。

`Math.abs` は２通りの使い方ができて、

```qml
Math.abs(-1deg) // 1deg になる
```

は動いたのですが、

```qml
-1deg.abs() // 1deg にならない
```
の場合は `-1deg` のままで動きませんでした。

後者の形式は上記のドキュメントに以下の記載があり、書き方はとても自然ですし動いても良さそうです。

> They can also be called directly as member function of numeric types. (For example angle.sin() or foo.mod(10)).

## サンプルコードでの確認

最初は自分の使い方が何か変なのかなと思ったのですが、シンプルなコードでも再現しました。

https://slintpad.com/?snippet=export+component+AbsAngle+inherits+VerticalLayout+%7B%0A++++Text+%7B%0A++++++++text%3A+-1deg.abs%28%29+%2F+1deg%3B++%2F%2F+%3D%3E+-1%0A++++%7D%0A++++Text+%7B%0A++++++++text%3A+Math.abs%28-1deg%29+%2F+1deg%3B+%2F%2F+%3D%3E+1%0A++++%7D%0A%7D&style=native

```qml
export component AbsAngle inherits VerticalLayout {
    Text {
        text: -1deg.abs() / 1deg;  // => -1
    }
    Text {
        text: Math.abs(-1deg) / 1deg; // => 1
    }
}
```

# バグ報告をしました

https://github.com/slint-ui/slint/issues/7146

Slint は GitHub 上で開発が行われていて、そこの Issue からバグの報告を受け付けています。

既に同じ問題が報告されていないかを確認した上で上記のバグ報告を書きました。


# 自分で直せないか調べてみました

[internal/compiler/builtin_macros.rs](https://github.com/slint-ui/slint/blob/dc4bda958ebfda6184825bdacf6cb0bbd6fc7bd2/internal/compiler/builtin_macros.rs#L171) にある以下の関数で処理をしているようです。

```rust:builtin_macros.rs
fn abs_macro(
    node: Option<NodeOrToken>,
    args: Vec<(Expression, Option<NodeOrToken>)>,
    diag: &mut BuildDiagnostics,
) -> Expression {
    if args.len() != 1 {
        diag.push_error("Needs 1 argument".into(), &node);
        return Expression::Invalid;
    }
    let ty = args[0].0.ty();
    let ty = if ty.default_unit().is_some() || matches!(ty, Type::UnitProduct(_)) {
        ty
    } else {
        Type::Float32
    };

    let source_location = node.map(|n| n.to_source_location());
    let function = Box::new(Expression::BuiltinFunctionReference(
        BuiltinFunction::Abs,
        source_location.clone(),
    ));
    if matches!(ty, Type::Float32) {
        let arguments =
            args.into_iter().map(|(e, n)| e.maybe_convert_to(ty.clone(), &n, diag)).collect();
        Expression::FunctionCall { function, arguments, source_location }
    } else {
        Expression::Cast {
            from: Expression::FunctionCall {
                function,
                arguments: args
                    .into_iter()
                    .map(|(a, _)| Expression::Cast { from: a.into(), to: Type::Float32 })
                    .collect(),
                source_location,
            }
            .into(),
            to: ty,
        }
    }
}
```

ここだけ見ても詳細は分かりませんが、`-1deg.abs()` の計算が `1deg.abs()` を先に処理してから `-` の演算をしているのかなと思ったので以下のコメントを追記しました。

> Is `-` applied to the result of `1deg.abs()`?

## すぐにフィードバックがありました

> Right, `-1deg.abs()` is the same as `-(1deg.abs())`.
> This is the same precedence rules as in Rust, C++, and so on.
> 
> We can't change the precedence rules because then then things like `-self.foo` wouldn't work anymore because that would become `(-self).foo`
> 
> If anything we could detect this specific case and have a warning.

仕様としては仕方ないっぽいですね。`-1deg.abs()` って書けたら自然で美しいんですが、それは難しいようなので `(-1deg).abs()` もしくは `Math.abs(-1deg)` のように書きましょう。

# おわりに

今回は Slint を利用していて変な挙動を見つけたので、バグ報告をしたことを記事にしました。
結果的には仕様ということでしたが、今後私以外の人が困ったときにこのバグレポにたどり着いたらいいなと思っています。あとは「警告くらいはだせるかも？」とのことなので、将来的に何かしらの改善がなされるのではと思っています。

まぁ普通の人は `-1deg.abs()` って書く機会はめったにないと思うんですけれど。。。

というわけで、みなさんも Slint を利用して変な挙動を見つけた際には是非バグ報告をしてみてください。

明日は @hermit4 さんによる [Slintのバックエンドとレンダラー](https://qiita.com/hermit4/items/262b316c5369db879d87) です。お楽しみに！