Skip to content

Intersection of Function with Object doesn't validate correctly #701

@niieani

Description

@niieani

🐛 Bug report

Current Behavior

decode() reports errors when trying to validate an intersection of a function with an object (i.e. a function with additional properties).

Expected behavior

No errors.

Reproducible example

https://codesandbox.io/s/io-ts-reproduction-2lcmt5

Expand full code
import * as t from "io-ts";

export const featureConfigProperty = Symbol("config");

const FeatureActionFnValidator = t.Function;
export type FeatureActionFn = t.TypeOf<typeof FeatureActionFnValidator>;

const FeatureConfigValidator = t.type({
  name: t.string
});

const FeatureDefinitionValidator = t.intersection([
  t.type({
    actionFn: FeatureActionFnValidator
  }),
  FeatureConfigValidator
]);
export type FeatureDefinition = t.TypeOf<typeof FeatureDefinitionValidator>;

const DefinedFeatureValidator = t.intersection([
  FeatureActionFnValidator,
  t.type({
    [featureConfigProperty]: FeatureConfigValidator
  })
]);
export type DefinedFeature = t.TypeOf<typeof DefinedFeatureValidator>;

export const defineFeature = ({
  actionFn,
  ...config
}: FeatureDefinition): DefinedFeature =>
  Object.assign(actionFn, { [featureConfigProperty]: config });

const definedFeature = defineFeature({
  actionFn: () => {},
  name: "a feature"
});

// this goes Left, even though it should validate
// it's a function with an additional property:
console.log(DefinedFeatureValidator.decode(definedFeature));

Suggested solution(s)

If an intersection contains mixed object with function, the decoder should only test for function type, and then validate the presence of properties.

My bet is that this code here is at fault (specifically, typeof u === 'object' fails):

io-ts/src/index.ts

Lines 987 to 1000 in 616583d

export class AnyDictionaryType extends Type<{ [key: string]: unknown }> {
/**
* @since 1.0.0
*/
readonly _tag: 'AnyDictionaryType' = 'AnyDictionaryType'
constructor() {
super(
'UnknownRecord',
(u): u is { [key: string]: unknown } => u !== null && typeof u === 'object' && !Array.isArray(u),
(u, c) => (this.is(u) ? success(u) : failure(u, c)),
identity
)
}
}

(also in https://github.com/gcanti/io-ts/blob/616583de0198632cad7820ed8701b15f654c7fd2/src/Guard.ts#L98C14-L100)

Additional context

Looks like a separate problem, but seems like Symbol properties aren't being validated correctly, ts-io treats them as if they didn't exist, though the TS types are correct. Might need to use Object.getOwnPropertySymbols in the code.

Your environment

Which versions of io-ts are affected by this issue? Did this work in previous versions of io-ts?

Software Version(s)
io-ts 2.2.20
fp-ts 2.13.1
TypeScript 5.1.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions