Skip to content

Fix Double prop defaults losing precision in generated Android delegates - #58070

Open
dennytosp wants to merge 1 commit into
react:mainfrom
dennytosp:fix/codegen-double-default-precision
Open

Fix Double prop defaults losing precision in generated Android delegates#58070
dennytosp wants to merge 1 commit into
react:mainfrom
dennytosp:fix/codegen-double-default-precision

Conversation

@dennytosp

Copy link
Copy Markdown
Contributor

Summary:

GeneratePropsJavaDelegate emits the default value for an optional DoubleTypeAnnotation prop with an f (float) suffix, but the setter it calls takes a double:

// GeneratePropsJavaDelegate.js
case 'DoubleTypeAnnotation':
  if (prop.optional) {
    return `value == null ? ${typeAnnotation.default}f : ((Double) value).doubleValue()`;
  }
// GeneratePropsJavaInterface.js — the setter this is passed to
case 'DoubleTypeAnnotation':
  return 'double value';

The f literal is parsed as a float, then widened back to double at the call site, so the default silently arrives rounded to float precision. It compiles without a warning, which is why it has gone unnoticed.

This is visible in the repo's own snapshots today. Same fixture, same prop, two platforms:

Generator Output for blurRadius3?: WithDefault<Double, 2.1>
GeneratePropsH (C++) double blurRadius3{2.1};
GeneratePropsJavaDelegate value == null ? 2.1f : ((Double) value).doubleValue()

So a component using the C++ renderer gets 2.1 and the same component on the Android delegate path gets 2.0999999046325684.

Compiling the generated shape confirms it:

static void setBlurRadius3(double value) { System.out.println(value); }

setBlurRadius3(value == null ? 2.1f : ...);        // 2.0999999046325684
setBlurRadius3(value == null ? 123456789f : ...);  // 1.23456792E8

The integer case is the clearest damage: any Double default above 2^24 is not representable as a float, so 123456789 arrives as 123456792. Fractional defaults are wrong from the first value that is not a dyadic rational — 0.1, 2.1, and the fixture's own 0.001 all shift.

FloatTypeAnnotation on the line below is correct — it targets a float setter, so f is right there. Only the Double branch has the wrong suffix, which is consistent with it having been copied from the Float branch.

Changed to a d suffix, mirroring the existing f for float rather than dropping the suffix entirely, so the literal's type is stated rather than left to numeric promotion.

DoubleTypeAnnotation is never boxed by codegen (it is always a primitive double, defaulting to Double.NaN when non-optional), so there is no nullable-double path to consider.

Changelog:

[ANDROID] [FIXED] - Fix Double prop defaults being rounded to float precision in generated ViewManager delegates

Test Plan:

The generator is snapshot-tested, so the snapshot is the test. Updating it changes only the Double component and leaves the Float component untouched:

  public class DoublePropNativeComponentManagerDelegate<T extends View, U extends ...
       case "blurRadius2":
-        mViewManager.setBlurRadius2(view, value == null ? 0.001f : ((Double) value).doubleValue());
+        mViewManager.setBlurRadius2(view, value == null ? 0.001d : ((Double) value).doubleValue());
       case "blurRadius3":
-        mViewManager.setBlurRadius3(view, value == null ? 2.1f : ((Double) value).doubleValue());
+        mViewManager.setBlurRadius3(view, value == null ? 2.1d : ((Double) value).doubleValue());
       case "blurRadius4":
-        mViewManager.setBlurRadius4(view, value == null ? 0f : ((Double) value).doubleValue());
+        mViewManager.setBlurRadius4(view, value == null ? 0d : ((Double) value).doubleValue());
       case "blurRadius5":
-        mViewManager.setBlurRadius5(view, value == null ? 1f : ((Double) value).doubleValue());
+        mViewManager.setBlurRadius5(view, value == null ? 1d : ((Double) value).doubleValue());
       case "blurRadius6":
-        mViewManager.setBlurRadius6(view, value == null ? 0f : ((Double) value).doubleValue());
+        mViewManager.setBlurRadius6(view, value == null ? 0d : ((Double) value).doubleValue());
$ yarn jest packages/react-native-codegen
Test Suites: 64 passed, 64 total
Tests:       3122 passed, 3122 total
Snapshots:   1277 passed, 1277 total

$ yarn flow-check
Found 0 errors

$ npx eslint packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js
(no output)

0d, 1d and 2.1d are valid Java double literals; the proof snippet above compiles and runs under java Proof.java.

No committed generated sources carry the old output — grep -rn "f : ((Double) value).doubleValue()" --include='*.java' --include='*.kt' returns nothing outside the snapshot.

GeneratePropsJavaDelegate emitted the default for an optional
DoubleTypeAnnotation prop with a 'f' (float) suffix, while the setter it
calls takes a 'double'. The float literal is widened back to double at
the call site, so the default silently arrives rounded to float
precision.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 22, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Aug 22, 2026
@meta-codesync

meta-codesync Bot commented Aug 22, 2026

Copy link
Copy Markdown

@fabriziocucci has imported this pull request. If you are a Meta employee, you can view this in D117082006.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant