Skip to content

Implement system macro make_decimal #986

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

Merged
merged 4 commits into from
Jul 2, 2025

Conversation

nirosys
Copy link
Contributor

@nirosys nirosys commented Jun 26, 2025

Issue #, if available: #942

Description of changes:
This PR implements the make_decimal system macro.

Running tests: ion-tests/conformance/system_macros/make_decimal.ion
  make_decimal can be invoked                              ...  [Ok]
  the first argument must be a single, non-null integer    ...  [Ok]
  the second argument must be a single, non-null integer   ...  [Ok]
  in binary both arguments are encoded as tagged values    ...  [Ok]
  make_decimal creates a single unannotated decimal        ...  [Ok]

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


/// Given a [`ValueExpr`], this function will expand it into its underlying value; An
/// error is return if the value does not expand to exactly one Int.
fn get_integer(&self, env: Environment<'top, D>, value: ValueExpr<'top, D>) -> IonResult<Int> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The need for this type of function has popped up a couple times now. Thinking about pulling it out into a standalone function, or something similarly reusable.

Copy link

codecov bot commented Jun 26, 2025

Codecov Report

Attention: Patch coverage is 81.44330% with 18 lines in your changes missing coverage. Please review.

Project coverage is 78.47%. Comparing base (8f03009) to head (3e068d0).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/lazy/expanded/macro_evaluator.rs 81.91% 1 Missing and 16 partials ⚠️
src/lazy/expanded/template.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #986   +/-   ##
=======================================
  Coverage   78.46%   78.47%           
=======================================
  Files         138      138           
  Lines       33290    33385   +95     
  Branches    33290    33385   +95     
=======================================
+ Hits        26121    26198   +77     
- Misses       5177     5179    +2     
- Partials     1992     2008   +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nirosys
Copy link
Contributor Author

nirosys commented Jun 26, 2025

New rust just dropped today.. clippy lints are going wild with the enabling of uninlined format args.

@nirosys
Copy link
Contributor Author

nirosys commented Jun 26, 2025

Will follow up with a PR for the clippy warnings. It's a bit much to include here, especially since they all seem unrelated to this PR.

@nirosys nirosys marked this pull request as ready for review June 26, 2025 23:47
let expo_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments; missing second argument"))?;
let exponent = self.get_integer(environment, expo_expr?)?;

let decimal = Decimal::new(coefficient, exponent.as_i64().ok_or_else(|| IonError::decoding_error("Unable to convert coefficient Int to i64"))?);
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth updating this error message to say something like "coefficient is out of the range supported by this implementation" so that it's clear that this is an intentional limitation in the library.

) -> IonResult<MacroExpansionStep<'top, D>> {
// Arguments should be: (coefficient exponent)
// Both coefficient and exponent should evaluate to a single integer value.
let coeff_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments"))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Small change to be explicit about the problem that was encountered.

Suggested change
let coeff_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments"))?;
let coeff_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments; found 0 arguments"))?;

let coeff_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments"))?;
let coefficient = self.get_integer(environment, coeff_expr?)?;

let expo_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments; missing second argument"))?;
Copy link
Contributor

Choose a reason for hiding this comment

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

(very optional) Minor suggestion to make the message consistent with my suggested change for the previous error message.

Suggested change
let expo_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments; missing second argument"))?;
let expo_expr = self.arguments.next().ok_or(IonError::decoding_error("`make_decimal` takes 2 integer arguments; found only 1 argument"))?;

let mut evaluator = MacroEvaluator::new_with_environment(env);
evaluator.push(invocation.expand()?);
let int_arg = match evaluator.next()? {
None => IonResult::decoding_error("`make_decimal` takes two integers as arguments; empty value found"),
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be a slight improvement to indicate which parameter was problematic, and it also prepares this for being generalized as a stand-alone helper function.

format!("`make_decimal` parameter {i} must be an integer, but argument was absent")

(And similarly for the "multiple values" case.)

@@ -3134,6 +3202,72 @@ mod tests {
)
}

#[test]
fn make_decimal_eexp() -> IonResult<()> {
stream_eq(
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor suggestion—make each expression in the test input produce a different value so that it's obvious where something may have gone wrong.

}

#[test]
fn make_decimal_arg_errors() -> IonResult<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to test for a case like (:make_decimal 1 2 3), or is checking the number of arguments handled elsewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The number of provided arguments is checked in the text parser itself, yea. I need to look though to see if the same is happening in the binary reader.


// A simple wrapper for make_decimal's known arguments. Provides context for error reporting, and
// functionality to expand e-exp and validate integer types.
struct MakeDecimalArgument<'top, D: Decoder>(&'static str, ValueExpr<'top, D>);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making this a little closer to reusable. I'll probably make it general use with the make_timestamp implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

What will you call it then? NamedIntegerArgument or something maybe? You'd have to add another field to name the context (macro) in the error messaging, and at that point you might as well name the fields 🤔...


// A simple wrapper for make_decimal's known arguments. Provides context for error reporting, and
// functionality to expand e-exp and validate integer types.
struct MakeDecimalArgument<'top, D: Decoder>(&'static str, ValueExpr<'top, D>);
Copy link
Contributor

Choose a reason for hiding this comment

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

What will you call it then? NamedIntegerArgument or something maybe? You'd have to add another field to name the context (macro) in the error messaging, and at that point you might as well name the fields 🤔...

@nirosys nirosys merged commit 79d6e05 into amazon-ion:main Jul 2, 2025
29 checks passed
@nirosys nirosys deleted the rgiliam/make_decimal_impl branch July 2, 2025 21:11
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.

3 participants