From c5cca382f42d67cd2b86b66121a33b6eeca3b45c Mon Sep 17 00:00:00 2001 From: David Chambers Date: Sun, 10 Apr 2016 15:26:50 -0700 Subject: [PATCH] permit the use of arrays as tuples It's not currently safe to use arrays as tuples due to a bug in the circular-reference detection in _determineActualTypes. Given a value such as ["abc", Right(123)] in an environment containing both Array and Pair (the type comprising all two-element arrays), the function is applied recursively to the nested values once for Array and once for Pair. This is fine in the case of "abc"; for Right(123), though, this is problematic. The first time, Right(123) is appended to $seen. The second time, the identical object is found in $seen, erroneously indicating a circular reference. This commit removes the mutation. --- index.js | 15 +++++++++------ test/index.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index b6a81bc..3985329 100644 --- a/index.js +++ b/index.js @@ -1009,27 +1009,30 @@ }); // _determineActualTypes :: (Boolean, [Object], [Any]) -> [Type] - var _determineActualTypes = function recur(loose, $seen, values) { + var _determineActualTypes = function recur(loose, seen, values) { if (isEmpty(values)) return [Unknown]; // typeses :: [[Type]] var typeses = map(values, function(value) { + var seen$; if (typeof value === 'object' && value != null || typeof value === 'function') { // Abort if a circular reference is encountered; add the current // object to the list of seen objects otherwise. - if ($seen.indexOf(value) >= 0) return []; - $seen.push(value); + if (seen.indexOf(value) >= 0) return []; + seen$ = seen.concat([value]); + } else { + seen$ = seen; } return chain(env, function(t) { return ( t.name === 'sanctuary-def/Nullable' || !test(t, value).valid ? [] : t.type === 'UNARY' ? - map(recur(loose, $seen, t._1(value)), UnaryType.from(t)) : + map(recur(loose, seen$, t._1(value)), UnaryType.from(t)) : t.type === 'BINARY' ? BinaryType.xprod(t, - recur(loose, $seen, t._1(value)), - recur(loose, $seen, t._2(value))) : + recur(loose, seen$, t._1(value)), + recur(loose, seen$, t._2(value))) : // else [t] ); diff --git a/test/index.js b/test/index.js index 575c932..8f3ddcb 100644 --- a/test/index.js +++ b/test/index.js @@ -1743,6 +1743,39 @@ describe('def', function() { 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); }); + it('permits the use of arrays as tuples', function() { + // Pair :: Type + var Pair = $.BinaryType( + 'my-package/Pair', + R.both(R.is(Array), R.propEq('length', 2)), + R.compose(R.of, R.nth(0)), + R.compose(R.of, R.nth(1)) + ); + + var env = $.env.concat([Either, Pair]); + var def = $.create(true, env); + + // id :: a -> a + var id = def('id', {}, [a, a], R.identity); + + eq(id(['abc', 123]), ['abc', 123]); + eq(id([Left('abc'), 123]), [Left('abc'), 123]); + eq(id(['abc', Right(123)]), ['abc', Right(123)]); + eq(id([Left('abc'), Right(123)]), [Left('abc'), Right(123)]); + + throws(function() { id([Left('abc'), 123, 456]); }, + errorEq(TypeError, + 'Type-variable constraint violation\n' + + '\n' + + 'id :: a -> a\n' + + ' ^\n' + + ' 1\n' + + '\n' + + '1) [Left("abc"), 123, 456] :: Array ???\n' + + '\n' + + 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); + }); + it('supports values of "foreign" types', function() { // id :: a -> a var id = def('id', {}, [a, a], R.identity);