Skip to content

frontend: simplify uses of comparison operators#466

Merged
mikesamuel merged 5 commits into
mainfrom
pre-cmp-refactoring
Jul 22, 2026
Merged

frontend: simplify uses of comparison operators#466
mikesamuel merged 5 commits into
mainfrom
pre-cmp-refactoring

Conversation

@mikesamuel

@mikesamuel mikesamuel commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This is preparatory work to changing the IR of comparison operators. Just making sure that non-overridden operators, especially the Int32 variants, are directly available.

Also, instead of using the == and != cover functions in other desugarings, use simpler tests like isNull.

This required changing frontend and backend test goldens so I thought I'd get it out of the way first.

Specific changes:

  • Coalescing operator, ??, now uses isNull in its desugaring, not != null.
  • Comparison operators like < are no longer null accepting. be-rust no longer wraps f64 as options to compare them but == and != still are.
  • CoverFunction.uncover is now a static method so users like new and DotHelper no longer have to create CoverFunctions under the hood.
  • Hardcoded ASTs in tests like ControlFlowTest use specific comparison operators, not the cover function values.

This is preparatory work to changing the IR of comparison
operators.  Just making sure that non-overridden operators,
especially the Int32 variants, are directly available.

Also, instead of using the `==` and `!=` cover functions
in other desugarings, use simpler tests like `isNull`.

This required changing frontend and backend test goldens
so I thought I'd get it out of the way first.

Specific changes:

- Coalescing operator, `??`, now uses `isNull` in its desugaring, not `!= null`.
- Comparison operators like `<` are no longer null accepting.
  be-rust no longer wraps f64 as options to compare them but
  `==` and `!=` still are.
- CoverFunction.uncover is now a static method so users like `new` and
  DotHelper no longer have to create CoverFunctions under the hood.
- Hardcoded ASTs in tests like ControlFlowTest use specific comparison
  operators, not the cover function values.

This is a draft.  There is a failure in be-rust.  Functional tests that depend on std, like typesDate fail.

The use of `.cmp` below is flagged as an error.

```rs
fn decodeHexUnsigned__361(sourceText__708: impl temper_core::ToArcString, start__709: usize, limit__710: usize) -> i32 {
    let sourceText__708 = sourceText__708.to_arc_string();
    let return__307: i32;
    let mut t___1642: bool;
    let mut t___1644: bool;
    let mut t___1647: bool;
    let mut t___1650: i32;
    'fn__711: {
        let mut n__712: i32 = 0;
        let mut i__713: usize = start__709;
        'loop___2712: loop {
            if ! (Some(i__713).cmp( & Some(limit__710)) as i32 < 0) {
                break;
            }
```

It corresponds to code from std/json:

```ts
    let decodeHexUnsigned(
      sourceText: String, start: StringIndex, limit: StringIndex
    ): Int {
      var n = 0;
      var i = start;
      while (i.compareTo(limit) < 0) {
```

Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
@mikesamuel

Copy link
Copy Markdown
Contributor Author

translator: RustTranslator,
): Rust.Expr {
val cmpFn = when (operator) {
RustOperator.Equals, RustOperator.NotEquals -> "cmp_option"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I want == and != for ints to eventually be non-null accepting, but for now, they still are.

BuiltinOperatorId.LtIntInt,
) { cmp ->
TBoolean.value(cmp < 0)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Exposing these out so that the non-overloaded version is readily available.

This will make it easier to desugar overloaded comparisons to a use of <=> nested inside a guaranteed int comparison of its output.

{
Call {
V(Value(BuiltinFuns.notEqualsFn))
V(vIsNullFn)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

One fewer use of a cover function.

("..." to OutputTokenType.Name) to false, // ...Int
(OutputTokenType.Word to "(") to true, // if (
(OutputTokenType.Name to "{") to true, // f {
(OutputTokenType.NumericValue to "{") to true, // if x == 0 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rust was formatting oddly: if x < 0{. Now there's a space before the bracket.

while (i.compareTo(limit) < 0) {
let cp = sourceText[i];
let digit = if (char'0' <= cp && cp <= char'0') {
let digit = if (char'0' <= cp && cp <= char'9') {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤦

accessibleMembers.isEmpty() && extensions.isNotEmpty() &&
extensions.all { (it as? FunctionResolution)?.fn is CallableValue }
) {
val cover = CoverFunction(extensions.map { (it as FunctionResolution).fn as CallableValue })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fewer creations of cover functions under the hood.

TODO("Link to a macro environment so we can evaluate the value tree")
val (_, newValue) = newValues[index]
?: TODO("Link to a macro environment so we can evaluate the value tree $index, $interpMode")
newValue.valueContained ?: Fail

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

new now cherry picks arguments to pass and the this argument reaches this path.

cb: InterpreterCallback,
interpMode: InterpMode,
covered: List<CallableValue>,
otherwise: CallableValue? = null,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only change from above is these two arguments passed in instead of being accessed as fields, and a change to the recursive call at L129.

tjpalmer added 2 commits July 21, 2026 16:56
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
| let mut i__0: usize = start__0;
| 'loop___0: loop {
| if ! (Some(Some(i__0).cmp( & Some(limit__0)) as i32) < Some(0)) {
| if ! (Some(i__0).cmp( & Some(limit__0)) as i32 < 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like the outer Some(...) was masking the now exposed as i32 < 0.

Address this error:

```rs
    --> C:\Users\work2\AppData\Local\Temp\temper-test3896171247360590248\rust\std\src\json\mod.rs:1782:64
     |
1782 |             if ! (Some(i__713).cmp( & Some(limit__710)) as i32 < 0) {
     |                                                                ^ -- interpreted as generic arguments
     |                                                                |
     |                                                                not interpreted as comparison
     |
help: try comparing the cast value
     |
1782 |             if ! ((Some(i__713).cmp( & Some(limit__710)) as i32) < 0) {
     |                   +                                            +
```
Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
@mikesamuel
mikesamuel marked this pull request as ready for review July 22, 2026 16:48
|pub fn hi(n__0: i32) {
| let mut i__0: i32 = 0;
| 'loop___0: while Some(i__0) < Some(n__0) {
| 'loop___0: while i__0 < n__0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Losing the Some is nice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah. Comparisons were just doing too much. Uses of non-nullable types shouldn't pay for null-safety.

@mikesamuel
mikesamuel merged commit 2912533 into main Jul 22, 2026
2 checks passed
@mikesamuel
mikesamuel deleted the pre-cmp-refactoring branch July 22, 2026 21:10
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