Skip to content

Fix R.clone returning the same reference for Error objects#3533

Merged
kedashoe merged 1 commit into
ramda:masterfrom
chatman-media:fix/clone-error-instance
Jun 28, 2026
Merged

Fix R.clone returning the same reference for Error objects#3533
kedashoe merged 1 commit into
ramda:masterfrom
chatman-media:fix/clone-error-instance

Conversation

@chatman-media

Copy link
Copy Markdown
Contributor

Summary

R.clone documents that it "creates a deep copy of the source that can be used in place of the source object without retaining any references to it." For Error values this is broken: type(value) returns 'Error', which is not handled in _clone's switch, so it falls through to default: return value and hands back the same error.

const e = new Error('boom');
R.clone(e) === e;        // true  (should be false)

const c = R.clone(e);
c.foo = 1;
e.foo;                    // 1  — mutating the "clone" mutates the original

This is issue #3390. A core contributor confirmed it there ("we do not attempt to handle Error") and suggested the fix should "create a new Error with some properties set on it" — which is what this PR does.

Fix

Add an 'Error' case to _clone that:

  • creates a fresh object from the source's prototype via Object.create(Object.getPrototypeOf(value)), so the subtype and instanceof are preserved (TypeError stays a TypeError, custom Error subclasses stay their class);
  • reuses the existing copy helper to deep-clone own enumerable properties and register the clone for circular-reference handling;
  • explicitly carries over the non-enumerable message, stack and errors (AggregateError) own properties, deep-cloning them like everything else.

Using Object.create on the prototype (rather than calling the constructor) avoids constructor side effects and correctly handles AggregateError, whose constructor takes an iterable of errors as its first argument.

Tests

Added regression tests to test/clone.js covering: no retained reference + deep-cloned own properties + preserved message/stack; prototype preservation for TypeError/RangeError/SyntaxError/ReferenceError; and AggregateError including its non-enumerable errors.

  • Without the fix, the new reference-equality and AggregateError tests fail (Expected "actual" not to be reference-equal to "expected").
  • With the fix, the full suite passes (1213 passing) and eslint is clean.

Closes #3390

R.clone fell through to the default switch branch for Error values and
returned the source error itself, so clone(error) === error and mutating
the result mutated the original, violating clone's contract.

Clone errors into a fresh object that shares the source prototype (so
subtypes and instanceof are preserved) and deep-clone own properties,
carrying over the non-enumerable message, stack and AggregateError errors.

Closes ramda#3390
@github-actions

Copy link
Copy Markdown
Coverage Summary
> ramda@0.32.0 coverage:summary
> BABEL_ENV=cjs nyc --reporter=text-summary mocha -- --reporter=min --require @babel/register

�[2J�[1;3H
1210 passing (855ms)


=============================== Coverage summary ===============================
Statements   : 94.04% ( 2523/2683 )
Branches     : 85.84% ( 982/1144 )
Functions    : 93.38% ( 564/604 )
Lines        : 94.3% ( 2365/2508 )
================================================================================

@kedashoe kedashoe merged commit bcb320e into ramda:master Jun 28, 2026
1 check passed
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.

clone does not clone error instance

2 participants