Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/captp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@endo/eventual-send": "workspace:^",
"@endo/marshal": "workspace:^",
"@endo/nat": "workspace:^",
"@endo/pass-style": "workspace:^",
"@endo/promise-kit": "workspace:^"
},
"bugs": {
Expand Down
4 changes: 2 additions & 2 deletions packages/captp/src/finalize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* global globalThis */
import { Far, isObject } from '@endo/marshal';
import { Far, isPrimitive } from '@endo/pass-style';

// @ts-check
const { WeakRef, FinalizationRegistry } = globalThis;
Expand Down Expand Up @@ -112,7 +112,7 @@ export const makeFinalizingMap = (finalizer, opts) => {
// Does deref, and thus does guarantee stability of both old and new values
// until the end of the turn.
set: (key, ref) => {
assert(isObject(ref));
assert(!isPrimitive(ref));
finalizingMap.delete(key);
const newWR = new WeakRef(ref);
keyToRef.set(key, newWR);
Expand Down
18 changes: 13 additions & 5 deletions packages/eventual-send/src/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ const ntypeof = specimen => (specimen === null ? 'null' : typeof specimen);
const onDelivery = makeMessageBreakpointTester('ENDO_DELIVERY_BREAKPOINTS');

/**
* TODO Consolidate with `isObject` that's currently in `@endo/marshal`
* TODO Consolidate with `isPrimitive` that's currently in `@endo/pass-style`.
* Layering constraints make this tricky, which is why we haven't yet figured
* out how to do this.
*
* @param {any} val
* @returns {boolean}
* @type {(val: unknown) => val is (undefined
* | null
* | boolean
* | number
* | bigint
* | string
* | symbol)}
*/
const isObject = val => Object(val) === val;
const isPrimitive = val =>
!val || (typeof val !== 'object' && typeof val !== 'function');

/**
* Prioritize symbols as earlier than strings.
Expand Down Expand Up @@ -57,7 +65,7 @@ export const getMethodNames = val => {
names.add(name);
}
}
if (!isObject(val)) {
if (isPrimitive(val)) {
break;
}
layer = getPrototypeOf(layer);
Expand Down
9 changes: 7 additions & 2 deletions packages/marshal/src/dot-membrane.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
/// <reference types="ses"/>

import { E } from '@endo/eventual-send';
import { isObject, getInterfaceOf, Far, passStyleOf } from '@endo/pass-style';
import {
isPrimitive,
getInterfaceOf,
Far,
passStyleOf,
} from '@endo/pass-style';
import { Fail } from '@endo/errors';
import { makeMarshal } from './marshal.js';

Expand Down Expand Up @@ -70,7 +75,7 @@ const makeConverter = (mirrorConverter = undefined) => {
// gc works more often.
const mineIf = passBack(yours);

assert(!isObject(optVerb));
assert(isPrimitive(optVerb));
const myArgs = passBack(harden(yourArgs));
let myResult;

Expand Down
6 changes: 3 additions & 3 deletions packages/marshal/src/encodeToCapData.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
passStyleOf,
isErrorLike,
makeTagged,
isObject,
isPrimitive,
getTag,
hasOwnPropertyOf,
assertPassableSymbol,
Expand Down Expand Up @@ -52,7 +52,7 @@ const hasQClass = encoded => hasOwnPropertyOf(encoded, QCLASS);
* @returns {boolean}
*/
const qclassMatches = (encoded, qclass) =>
isObject(encoded) &&
!isPrimitive(encoded) &&
!isArray(encoded) &&
hasQClass(encoded) &&
encoded[QCLASS] === qclass;
Expand Down Expand Up @@ -323,7 +323,7 @@ export const makeDecodeFromCapData = (decodeOptions = {}) => {
* @param {Encoding} jsonEncoded must be hardened
*/
const decodeFromCapData = jsonEncoded => {
if (!isObject(jsonEncoded)) {
if (isPrimitive(jsonEncoded)) {
// primitives pass through
return jsonEncoded;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/marshal/src/marshal-justin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { q, X, Fail } from '@endo/errors';
import { Nat } from '@endo/nat';
import {
getErrorConstructor,
isObject,
isPrimitive,
nameForPassableSymbol,
passableSymbolForName,
} from '@endo/pass-style';
Expand Down Expand Up @@ -144,7 +144,7 @@ const decodeToJustin = (encoding, shouldIndent = false, slots = []) => {
* @returns {void}
*/
const prepare = rawTree => {
if (!isObject(rawTree)) {
if (isPrimitive(rawTree)) {
return;
}
// Assertions of the above to narrow the type.
Expand Down Expand Up @@ -292,7 +292,7 @@ const decodeToJustin = (encoding, shouldIndent = false, slots = []) => {
* @returns {number}
*/
const recur = rawTree => {
if (!isObject(rawTree)) {
if (isPrimitive(rawTree)) {
// primitives get quoted
return out.next(quote(rawTree));
}
Expand Down
3 changes: 2 additions & 1 deletion packages/module-source/test/fixtures/exportheavy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { mapIterable, filterIterable } from './src/helpers/iter-helpers.js';
export {
PASS_STYLE,
isObject,
isPrimitive,
assertChecker,
getTag,
hasOwnPropertyOf,
Expand Down Expand Up @@ -43,7 +44,7 @@ export {
export * from './src/types.js';


const { details: X, Fail } = assert;
export { X, Fail } from '@endo/errors';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a pure-refactor drive-by


// This is a pathological minimum, but exercised by the unit test.
export const MIN_DATA_BUFFER_LENGTH = 1;
Expand Down
4 changes: 4 additions & 0 deletions packages/pass-style/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
User-visible changes in `@endo/pass-style`:

# Next release

The exported function name `isObject` is ambiguous. It is unclear whether it includes functions or not. (It does.) To avoid this confusion, we're deprecating `isObject` and suggesting to use the new export `isPrimitive` instead, that has the opposite answer. IOW, for all `x`, `isObject(x) === !isPrimitive(x)`

# v1.6.2 (2024-06-17)

- Fixes, without qualification, so that the package initializes on platforms
Expand Down
1 change: 1 addition & 0 deletions packages/pass-style/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { mapIterable, filterIterable } from './src/iter-helpers.js';
export {
PASS_STYLE,
isObject,
isPrimitive,
assertChecker,
getTag,
hasOwnPropertyOf,
Expand Down
30 changes: 24 additions & 6 deletions packages/pass-style/src/passStyle-helpers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/// <reference types="ses"/>

/** @import {Checker} from './types.js' */
/** @import {PassStyle} from './types.js' */

import { X, q } from '@endo/errors';

/**
* @import {Checker, PassStyle, JSPrimitive} from './types.js';
*/

const { isArray } = Array;
const { prototype: functionPrototype } = Function;
const {
Expand All @@ -30,8 +31,25 @@ export const hasOwnPropertyOf = (obj, prop) =>
apply(objectHasOwnProperty, obj, [prop]);
harden(hasOwnPropertyOf);

// TODO try typing this; `=> val is {} too narrow, implies no properties
export const isObject = val => Object(val) === val;
/**
* @type {(val: unknown) => val is JSPrimitive}
*/
export const isPrimitive = val =>
// Safer would be `Object(val) !== val` but is too expensive on XS.
// So instead we use this adhoc set of type tests. But this is not safe in
// the face of possible evolution of the language. Beware!
!val || (typeof val !== 'object' && typeof val !== 'function');
harden(isPrimitive);

/**
* @deprecated use `!isPrimitive` instead
* @type {(val: unknown) => val is (Function | Record<string | symbol, unknown>)}
*/
export const isObject = val =>
Comment thread
erights marked this conversation as resolved.
// Safer would be `Object(val) -== val` but is too expensive on XS.
// So instead we use this adhoc set of type tests. But this is not safe in
// the face of possible evolution of the language. Beware!
!!val && (typeof val === 'object' || typeof val === 'function');
harden(isObject);

/**
Expand Down Expand Up @@ -162,7 +180,7 @@ const makeCheckTagRecord = checkProto => {
*/
const checkTagRecord = (tagRecord, expectedPassStyle, check) => {
return (
(isObject(tagRecord) ||
(!isPrimitive(tagRecord) ||
(!!check &&
CX(check)`A non-object cannot be a tagRecord: ${tagRecord}`)) &&
(isFrozen(tagRecord) ||
Expand Down
14 changes: 7 additions & 7 deletions packages/pass-style/src/passStyleOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { isPromise } from '@endo/promise-kit';
import { X, Fail, q, annotateError, makeError } from '@endo/errors';
import {
isObject,
isPrimitive,
isTypedArray,
PASS_STYLE,
assertChecker,
Expand Down Expand Up @@ -123,7 +123,7 @@ const makePassStyleOf = passStyleHelpers => {
const inProgress = new Set();

const passStyleOfRecur = inner => {
const innerIsObject = isObject(inner);
const innerIsObject = !isPrimitive(inner);
if (innerIsObject) {
const innerStyle = passStyleMemo.get(inner);
if (innerStyle) {
Expand Down Expand Up @@ -359,11 +359,11 @@ export const toThrowable = specimen => {
if (isErrorLike(specimen)) {
return toPassableError(/** @type {Error} */ (specimen));
}
// Note that this step will fail if `specimen` would be a passable container
// except that it contains non-passable errors that could be converted.
// This will need to be fixed to do the TODO above.
const passStyle = passStyleOf(specimen);
if (isObject(specimen)) {
if (!isPrimitive(specimen)) {
// Note that this step will fail if `specimen` would be a passable container
// except that it contains non-passable errors that could be converted.
// This will need to be fixed to do the TODO above.
const passStyle = /** @type {PassStyle} */ (passStyleOf(specimen));

@erights erights Jun 24, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moving the call to passStyleOf inside the conditional is a drive-by. But typescript's imprecision about the negation of an is result type provoked the extra cast.

switch (passStyle) {
case 'copyArray': {
const elements = /** @type {CopyArray} */ (specimen);
Expand Down
8 changes: 4 additions & 4 deletions packages/pass-style/src/remotable.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
PASS_STYLE,
checkTagRecord,
checkFunctionTagRecord,
isObject,
isPrimitive,
getTag,
CX,
} from './passStyle-helpers.js';
Expand Down Expand Up @@ -70,7 +70,7 @@ harden(assertIface);
* @returns {boolean}
*/
const checkRemotableProtoOf = (original, check) => {
isObject(original) ||
!isPrimitive(original) ||
Fail`Remotables must be objects or functions: ${original}`;

// A valid remotable object must inherit from a "tag record" -- a
Expand Down Expand Up @@ -181,7 +181,7 @@ const checkRemotable = (val, check) => {
*/
export const getInterfaceOf = val => {
if (
!isObject(val) ||
isPrimitive(val) ||
val[PASS_STYLE] !== 'remotable' ||
!checkRemotable(val)
) {
Expand All @@ -202,7 +202,7 @@ export const RemotableHelper = harden({

canBeValid: (candidate, check = undefined) => {
const validType =
(isObject(candidate) ||
(!isPrimitive(candidate) ||
(!!check &&
CX(check)`cannot serialize non-objects as Remotable ${candidate}`)) &&
(!isArray(candidate) ||
Expand Down
15 changes: 14 additions & 1 deletion packages/pass-style/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,24 @@ export type AtomStyle =
| 'byteArray'
| 'symbol';

/**
* The actual JS primitive types.
*/
export type JSPrimitive =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Made it easier to define the type of isPrimitive.

| undefined
| null
| boolean
| number
| bigint
| string
| symbol;

/**
* @deprecated Use `Atom` instead, which is also the ocapn name. Now that
* ByteArray has been added, this category no longer corresponds to
* JS primitives, We also expect to move to a passable-symbol representation
* as a JS object, not a JS primitive
* as a JS object, not a JS primitive. But if you really do mean a JS primitive
* only, use `JSPrimitive` instead.
*/
export type Primitive = Atom;

Expand Down
18 changes: 16 additions & 2 deletions packages/promise-kit/src/memo-race.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,21 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/

const isObject = value => Object(value) === value;
/**
* TODO Consolidate with `isPrimitive` that's currently in `@endo/pass-style`.
* Layering constraints make this tricky, which is why we haven't yet figured
* out how to do this.
*
* @type {(val: unknown) => val is (undefined
* | null
* | boolean
* | number
* | bigint
* | string
* | symbol)}
*/
const isPrimitive = val =>
!val || (typeof val !== 'object' && typeof val !== 'function');

/**
* @template [T=any]
Expand Down Expand Up @@ -73,7 +87,7 @@ const markSettled = record => {
* @returns {PromiseMemoRecord}
*/
const getMemoRecord = value => {
if (!isObject(value)) {
if (isPrimitive(value)) {
// If the contender is a primitive, attempting to use it as a key in the
// weakmap would throw an error. Luckily, it is safe to call
// `Promise.resolve(contender).then` on a primitive value multiple times
Expand Down
27 changes: 12 additions & 15 deletions packages/ses/src/commons.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,23 +272,20 @@ export const getConstructorOf = fn =>
reflectGet(getPrototypeOf(fn), 'constructor');

/**
* isObject tests whether a value is an object.
* Today, this is equivalent to:
* TODO Consolidate with `isPrimitive` that's currently in `@endo/pass-style`.
* Layering constraints make this tricky, which is why we haven't yet figured
* out how to do this.
*
* const isObject = value => {
* if (value === null) return false;
* const type = typeof value;
* return type === 'object' || type === 'function';
* };
*
* But this is not safe in the face of possible evolution of the language, for
* example new types or semantics of records and tuples.
* We use this implementation despite the unnecessary allocation implied by
* attempting to box a primitive.
*
* @param {any} value
* @type {(val: unknown) => val is (undefined
* | null
* | boolean
* | number
* | bigint
* | string
* | symbol)}
*/
export const isObject = value => Object(value) === value;
export const isPrimitive = val =>
!val || (typeof val !== 'object' && typeof val !== 'function');

/**
* isError tests whether an object inherits from the intrinsic
Expand Down
Loading