-
Notifications
You must be signed in to change notification settings - Fork 154
Fix Elixir 1.19 warnings #427
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
Fix Elixir 1.19 warnings #427
Conversation
mix.exs
Outdated
|
|
||
| @source_url "https://github.com/elixir-protobuf/protobuf" | ||
| @version "0.16.0" | ||
| @version "0.16.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't release versions in PRs. I'll revert this and release when appropriate.
whatyouhide
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this!
| nanos_raw <- integer(1..999_999_999) do | ||
| max_range = String.to_integer(String.duplicate("9", digits_count)) | ||
| scale = round(:math.pow(10, 9 - digits_count)) | ||
| nanos = rem(nanos_raw - 1, max_range) + 1 | ||
| scaled_nanos = nanos * scale | ||
|
|
||
| real_nanos = | ||
| String.to_integer(String.pad_trailing(Integer.to_string(scaled_nanos), 9, "0")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on the changes here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the calcs into the test body instead of the generator clause, which were causing the warnings on Elixir 1.19.4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RFC3339 Property Test Changes
The Warning
warning: this clause in cond will always match:
max_range = String.to_integer(String.duplicate("9", digits_count))
since it has type:
integer()
where "digits_count" was given the type:
# type: dynamic()
# from: test/protobuf/json/rfc3339_test.exs:66
digits_count = generated_value
where "max_range" was given the type:
# type: integer()
# from: test/protobuf/json/rfc3339_test.exs:67:27
max_range = String.to_integer(String.duplicate("9", digits_count))
typing violation found at:
67 │ max_range = String.to_integer(String.duplicate("9", digits_count)),A second warning with the same shape is emitted for nanos = nanos * round(:math.pow(10, 9 - digits_count)) in the same check all, and the same pair of warnings appears in the encode property.
Why Elixir 1.19 Warns About This
Elixir 1.19’s new type-system warnings include detection of always-true/never-true clauses in cond/case (see the set-theoretic type system work). ExUnitProperties.check_all expands the generator list into a cond-like pipeline. When you use bindings like max_range = ... or rebinding nanos = ... inside the generator list, those become cond clauses that are always truthy, so the compiler flags them as “will always match.” This is the same warning reported by StreamData users in property tests with check all bindings.
What The Fix Does
In test/protobuf/json/rfc3339_test.exs, I made the generator list “pure generators” only (digits_count and nanos_raw), and moved the deterministic computations into the test body. That avoids = bindings inside the generator list, which is what triggers the cond warning. I also map nanos_raw into the desired 1..max_range range via rem, then apply the same scaling math as before.
Semantic Equivalence
The set of values covered is the same: nanos still ranges over 1..max_range, and scaled_nanos is still nanos * 10^(9 - digits_count) before converting into real_nanos. The only behavioral change is distribution (a slight modulo bias for the 6-digit case) and potentially different shrinking paths, which doesn’t affect correctness of the property. Coverage remains effectively the same.
References
|
|
||
| message = base_mod.put_extension(message, Bugs.PbExtension, :top_first_name, "TFN") | ||
| assert base_mod.get_extension(message, Bugs.PbExtension, :top_first_name) == "TFN" | ||
| message = apply(base_mod, :put_extension, [message, Bugs.PbExtension, :top_first_name, "TFN"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elixir 1.19.4 was warning about syntax when calling functions on a module stored in a var
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dynamic Module Call Changes
The Warning
warning: Bugs.Base.put_extension/4 is undefined (module Bugs.Base is not available or is yet to be defined)
│
260 │ message = base_mod.put_extension(message, Bugs.PbExtension, :top_first_name, "TFN")
│ ~
└─ test/protobuf/protoc/cli_integration_test.exs:260:24: Protobuf.Protoc.CLIIntegrationTest."test multiple extensions
defined in different scopes"/1A corresponding warning is emitted for Bugs.Base.get_extension/3.
Why Elixir 1.19 Warns About This
In this test, base_mod is bound via Bugs.Base = base_mod, so the compiler can infer that base_mod is the Bugs.Base module. With Elixir 1.19’s expanded type/remote-call analysis, the compiler attempts to resolve the remote call at compile time and warns if that module (generated later at runtime) is not yet available. This is part of the same type system work that started tracking variable types and remote calls more aggressively, and it surfaces as "module is not available or is yet to be defined."
The Recommended Fix
apply/3 is the standard way to invoke a function on a module that is only known at runtime. It keeps the call dynamic and avoids compile-time resolution, while preserving runtime behavior. In this case, the module is generated during the test run (compile_file_and_clean_modules_on_exit), so apply/3 is the appropriate pattern.
References
README.md
Outdated
| def deps do | ||
| [ | ||
| {:protobuf, "~> 0.15.0"} | ||
| {:protobuf, "~> 0.16.1"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's revert this too please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed but changed it to 0.16.0, not 0.15.0. Would you prefer 0.15.0? I'll change it according to your req's
README.md
Outdated
|
|
||
| ```bash | ||
| $ mix escript.install hex protobuf 0.14.0 | ||
| $ mix escript.install hex protobuf 0.16.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed this, but again, I reverted this to 0.16.0 since that's the most recent release. If that's wrong and needs to go back to 0.14.0, LMK and I'll fix it. Thanks.
CHANGELOG.md
Outdated
| ## v0.16.1 | ||
|
|
||
| ### Enhancements | ||
|
|
||
| * Add Elixir 1.19/OTP 28 to the CI matrix. | ||
| * Update README version references to the 0.16.x series. | ||
|
|
||
| ### Bug fixes | ||
|
|
||
| * Fix compilation warnings on Elixir 1.19+ by using struct pattern matching and map update syntax. | ||
| * Fix `preferred_cli_env` deprecation warning by moving configuration into `cli/0`. | ||
| * Fix Elixir 1.19 warnings in RFC3339 property tests and protoc CLI integration tests. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove the CHANGELOG please, most of these are not information that users will do anything with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Additional Notes
|
|
Thanks @nshkrdotcom 🫶 |
Summary
Testing