Skip to content

Conversation

@abs0luty
Copy link
Contributor

@abs0luty abs0luty commented Nov 4, 2025

@abs0luty abs0luty changed the title Fix false-positive redundant comparisons warning Fix false-positive "redundant comparison" warning Nov 4, 2025
@abs0luty abs0luty force-pushed the fix-false-positive-redundant-comparisons-warning branch 2 times, most recently from 3218098 to 2c061d4 Compare November 4, 2025 16:19
@giacomocavalieri
Copy link
Member

I think this is not the right way to solve this, we should change the code generation for js so that they are indeed the same. I think this is already the case on the erlang target

@abs0luty
Copy link
Contributor Author

abs0luty commented Nov 4, 2025

@giacomocavalieri I think the behavior is consistent across all targets (js and erlang). That's why I thought this was expected and agreed upon (functions are treated as objects and are compared by their unique memory references):

type Comparison {
  Wibble
  Wobble(String)
}

pub fn main() {
  echo Wibble == Wibble
  echo Wobble == Wobble
  echo fn() {} == fn() {}
  echo fn() { 3 } == fn() { 3 }
}
% gleam run --target javascript
...   

  Compiled in 0.02s
    Running hello_world.main
src/hello_world.gleam:7: True
src/hello_world.gleam:8: False
src/hello_world.gleam:9: False
src/hello_world.gleam:10: False
% gleam run --target erlang    
... Same output

@abs0luty
Copy link
Contributor Author

abs0luty commented Nov 4, 2025

Oh wait... we definitely should change the way we handle function comparisons:

type A {
  A(String)
}

fn a() {}

pub fn main() {
  echo a == a // True
  echo A == A // False
}

I’m unsure what the right criteria for function equality should be. For example, should two identically defined anonymous functions count as equal, or only named functions/constructors?

My assumption is one would expect memory references to be checked, and as two anonymous functions are distinct objects only functions with the same name would compare as equal.

@abs0luty abs0luty changed the title Fix false-positive "redundant comparison" warning Fix function comparisons Nov 4, 2025
@abs0luty abs0luty changed the title Fix function comparisons Fix inconsistent function comparisons behaviour Nov 4, 2025
@abs0luty abs0luty force-pushed the fix-false-positive-redundant-comparisons-warning branch 2 times, most recently from aee0fd9 to 4247ce5 Compare November 6, 2025 09:26
@abs0luty abs0luty changed the title Fix inconsistent function comparisons behaviour Fix record constructor functions code generation Nov 6, 2025
@abs0luty
Copy link
Contributor Author

abs0luty commented Nov 6, 2025

I considered manually changing how constructor functions are compared at runtime instead of making a breaking change to codegen. However, tracking dynamic code flow at runtime would add unnecessary overhead during compilation. In my opinion, modifying codegen is the better approach:

type Bar {
  Foo(String)
}

fn a() {}

pub fn main() {
  let b = a
  let c = a
  
  // because of modified codegen, d and f are now references 
  // to the same named function associated with Foo
  let d = Foo
  let f = Foo

  echo b == c // True
  echo d == f // False -> True
}

@abs0luty abs0luty force-pushed the fix-false-positive-redundant-comparisons-warning branch 3 times, most recently from b1ecc46 to 6d9bc19 Compare November 6, 2025 10:09
@lpil
Copy link
Member

lpil commented Nov 6, 2025

we should change the code generation for js so that they are indeed the same. I think this is already the case on the erlang target

For performance reasons we want to introduce singleton values for record constructors and for zero arity variants, which would solve this too.

This bit I'm unsure about is function capturing and anonymous functions. Do we have this issue with them today?

@abs0luty abs0luty force-pushed the fix-false-positive-redundant-comparisons-warning branch 7 times, most recently from 92ecfe7 to 567141c Compare November 8, 2025 08:50
@abs0luty
Copy link
Contributor Author

abs0luty commented Nov 8, 2025

@giacomocavalieri @lpil I've updated code generation so record constructors are emitted as singletons, giving them the same equality semantics as named functions. I want to know your opinion on the changes and naming strategy I used:

Erlang

Constructor functions with arguments use type_name$constructor_name naming where both parts are snake_case. For example, Comparison.Wobble becomes 'comparison$wobble'/1. These are generated as top-level Erlang functions that construct the appropriate tagged tuple. All constructor functions are unconditionally exported with their arity, even for private types, because they may be used within the module.

When a constructor is referenced as a value, the compiler generates fun 'comparison$wobble'/1 for same-module references or fun module:'comparison$wobble'/1 for cross-module references. The dollar sign separator ensures no collision with user-defined functions since Gleam function names cannot contain dollar signs.

JavaScript

Constructor functions use TypeName$ConstructorName naming where the type name is in PascalCase and the constructor name preserves its original casing. For example, Comparison.Wobble becomes export const Comparison$Wobble = ($0) => new Wobble($0). These are exported as named arrow functions at module level.

When referenced as a value, the compiler simply uses the identifier Comparison$Wobble directly instead of wrapping it in an arrow function.

The potential problem I see is Gleam users seeing compiler errors due to cached old codegen output when updating to next release. Not sure what to do with this.

@lpil
Copy link
Member

lpil commented Nov 10, 2025

Thank you for the detail! We don't want to make any changes to the API of generated Erlang modules, so we cannot add constructor functions. I think we should disable this warning for record constructor functions, the same way we do for anonymous functions.

@abs0luty
Copy link
Contributor Author

abs0luty commented Nov 10, 2025

type A {
  A(String)
}

fn a() {}

pub fn main() {
  echo a == a // True
  echo A == A // False
}

@lpil I think I overstated the risk of people hitting compiler errors due to cached codegen when upgrading.

The change in API of erlang and javascript modules is not breaking in the sense that it doesn't modify any existing definitions, it's just adding new functions for constructors? For instance, resolving issues like #5127 (where some unused types should not be emitted) would require such changes.

The PR with minor changes (like if there is any definition for constructor function cause recompiling) will not break any existing code, so I guess it's fine?

@abs0luty abs0luty force-pushed the fix-false-positive-redundant-comparisons-warning branch from 567141c to ebb9951 Compare November 10, 2025 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.

Language server incorrectly warns about comparison being True

3 participants