Skip to content

Conversation

@nshkrdotcom
Copy link
Contributor

Summary

  • fix Elixir 1.19 compile/test warnings by updating struct updates, property tests, and dynamic module calls
  • move preferred_cli_env into cli/0
  • add Elixir 1.19/OTP 28 to CI matrix

Testing

  • not run (not requested)

mix.exs Outdated

@source_url "https://github.com/elixir-protobuf/protobuf"
@version "0.16.0"
@version "0.16.1"
Copy link
Collaborator

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.

Copy link
Collaborator

@whatyouhide whatyouhide left a 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!

Comment on lines +67 to +74
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"))
Copy link
Collaborator

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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"])
Copy link
Collaborator

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?

Copy link
Contributor Author

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

Copy link
Contributor Author

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"/1

A 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"}
Copy link
Collaborator

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.

Copy link
Contributor Author

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

And this

Copy link
Contributor Author

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
Comment on lines 3 to 15
## 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.

Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@nshkrdotcom
Copy link
Contributor Author

Additional Notes

  • The struct-update changes in lib/protobuf/dsl.ex, lib/protobuf/protoc/cli.ex, lib/protobuf/protoc/generator.ex, and lib/protobuf/protoc/generator/extension.ex are directly responding to new 1.19 type warnings like “a struct for Protobuf.FieldProps is expected on struct update”; adding %Struct{} = var patterns and switching to %{var | ...} keeps behavior intact while satisfying the type checker.
  • mix.exs now defines def cli/0 with preferred_envs, matching the Mix 1.19 deprecation warning about :preferred_cli_env in def project.
  • .github/workflows/main.yml adds an Elixir 1.19.4 / OTP 28 matrix entry to validate the warnings are gone in CI.
  • No changes in lib/protobuf/encoder.ex or lib/protobuf/decoder.ex in c3d0d73.
  • Verification: on base 6379e87, Elixir 1.19.4 emits the two warning classes above; on this branch, ASDF_ELIXIR_VERSION=1.19.4-otp-28 mix test test/protobuf/json/rfc3339_test.exs and ASDF_ELIXIR_VERSION=1.19.4-otp-28 mix test test/protobuf/protoc/cli_integration_test.exs run clean.

@whatyouhide whatyouhide merged commit 4328993 into elixir-protobuf:main Jan 19, 2026
5 checks passed
@whatyouhide
Copy link
Collaborator

Thanks @nshkrdotcom 🫶

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