diff --git a/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-1.js b/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-1.js new file mode 100644 index 0000000..e87e59d --- /dev/null +++ b/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-1.js @@ -0,0 +1,42 @@ +/* @flow */ + +import type TypeContext from '../../../TypeContext'; + +function defineClass(t: TypeContext) { + let _t$TypeParametersSymb; + var _dec, _class, _class2, _temp; + const _TestClassTypeParametersSymbol = Symbol("TestClassTypeParameters"); + + let TestClass = (_dec = t.annotate(t.class("TestClass", TestClass => { + const ValueType = TestClass.typeParameter("ValueType"); + return [t.method("testMethod")]; + })), _dec(_class = (_temp = (_t$TypeParametersSymb = t.TypeParametersSymbol, _class2 = class TestClass { + constructor() { + this[_TestClassTypeParametersSymbol] = { + ValueType: t.typeParameter("ValueType") + }; + } + + testMethod() { + const items = t.array(this[_TestClassTypeParametersSymbol].ValueType) + .assert(JSON.parse('[{ "a": null, "b": "b" }, { "a": "a", "b": null }]')); + return items; + } + + }), _class2[_t$TypeParametersSymb] = _TestClassTypeParametersSymbol, _temp)) || _class); + return TestClass; +} + +// will work for const test = TestClass(); +export function pass(t: TypeContext) { + const TestClass = defineClass(t); + const testClass = t.ref(TestClass, t.any()).assert(new TestClass()); + return testClass.testMethod(); +} + +// will fail for const test = TestClass(); +export function fail(t: TypeContext) { + const TestClass = defineClass(t); + const testClass = t.ref(TestClass).assert(new TestClass()); + return testClass.testMethod(); +} diff --git a/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-2.js b/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-2.js new file mode 100644 index 0000000..b14fb71 --- /dev/null +++ b/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-2.js @@ -0,0 +1,46 @@ +/* @flow */ + +import type TypeContext from '../../../TypeContext'; + +function defineClass(t: TypeContext) { + let _t$TypeParametersSymb; + var _dec, _class, _class2, _temp; + const _TestClassTypeParametersSymbol = Symbol("TestClassTypeParameters"); + + let TestClass = (_dec = t.annotate(t.class("TestClass", TestClass => { + const Value1Type = TestClass.typeParameter("ValueType1"); + const Value2Type = TestClass.typeParameter("ValueType2"); + return [t.method("testMethod")]; + })), _dec(_class = (_temp = (_t$TypeParametersSymb = t.TypeParametersSymbol, _class2 = class TestClass { + constructor() { + this[_TestClassTypeParametersSymbol] = { + ValueType1: t.typeParameter("ValueType1"), + ValueType2: t.typeParameter("ValueType2") + }; + } + + testMethod() { + const items1 = t.array(this[_TestClassTypeParametersSymbol].ValueType1) + .assert(JSON.parse('[{ "a": null, "b": "b" }, { "a": "a", "b": null }]')); + const items2 = t.array(this[_TestClassTypeParametersSymbol].ValueType2) + .assert(JSON.parse('[{ "a": null, "b": "b" }, { "a": "a", "b": null }]')); + return items1.concat(items2); + } + + }), _class2[_t$TypeParametersSymb] = _TestClassTypeParametersSymbol, _temp)) || _class); + return TestClass; +} + +// will work for const test = TestClass(); +export function pass(t: TypeContext) { + const TestClass = defineClass(t); + const testClass = t.ref(TestClass, t.any(), t.any()).assert(new TestClass()); + return testClass.testMethod(); +} + +// will fail for const test = TestClass(); +export function fail(t: TypeContext) { + const TestClass = defineClass(t); + const testClass = t.ref(TestClass).assert(new TestClass()); + return testClass.testMethod(); +} diff --git a/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-3.js b/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-3.js new file mode 100644 index 0000000..230e1fa --- /dev/null +++ b/packages/flow-runtime/src/__tests__/__fixtures__/bugs/237-generic-arrays-3.js @@ -0,0 +1,42 @@ +/* @flow */ + +import type TypeContext from '../../../TypeContext'; + +function defineClass(t: TypeContext) { + let _t$TypeParametersSymb; + var _dec, _class, _class2, _temp; + const _TestClassTypeParametersSymbol = Symbol("TestClassTypeParameters"); + + let TestClass = (_dec = t.annotate(t.class("TestClass", TestClass => { + const ValueType = TestClass.typeParameter("ValueType"); + return [t.method("testMethod")]; + })), _dec(_class = (_temp = (_t$TypeParametersSymb = t.TypeParametersSymbol, _class2 = class TestClass { + constructor() { + this[_TestClassTypeParametersSymbol] = { + ValueType: t.typeParameter("ValueType"), + }; + } + + testMethod( toParse ) { + return t.array(this[_TestClassTypeParametersSymbol].ValueType).assert(JSON.parse(toParse)); + } + + }), _class2[_t$TypeParametersSymb] = _TestClassTypeParametersSymbol, _temp)) || _class); + return TestClass; +} + +export function pass(t: TypeContext) { + const TestClass = defineClass(t); + + t.ref(TestClass, t.string()).assert(new TestClass()).testMethod('["string1", "string2"]'); + t.ref(TestClass, t.number()).assert(new TestClass()).testMethod('[0, 1, 2, 3]'); + t.ref(TestClass, t.boolean()).assert(new TestClass()).testMethod('[false, true]'); + return true; +} + +// internal type still used for type validation inside testMethod() +export function fail(t: TypeContext) { + const TestClass = defineClass(t); + t.ref(TestClass, t.number()).assert(new TestClass()) + .testMethod('["string1", "string2"]'); +} diff --git a/packages/flow-runtime/src/types/TypeParameter.js b/packages/flow-runtime/src/types/TypeParameter.js index 04ebb36..5c5ba88 100644 --- a/packages/flow-runtime/src/types/TypeParameter.js +++ b/packages/flow-runtime/src/types/TypeParameter.js @@ -4,7 +4,7 @@ import Type from './Type'; import compareTypes from '../compareTypes'; import FlowIntoType from "./FlowIntoType"; import TypeAlias from './TypeAlias'; -import TypeParameterApplication from './TypeParameterApplication' +import TypeParameterApplication from './TypeParameterApplication'; import type Validation, {ErrorTuple, IdentifierPath} from '../Validation'; diff --git a/packages/flow-runtime/src/types/TypeParameterApplication.js b/packages/flow-runtime/src/types/TypeParameterApplication.js index 5a1967b..68bee64 100644 --- a/packages/flow-runtime/src/types/TypeParameterApplication.js +++ b/packages/flow-runtime/src/types/TypeParameterApplication.js @@ -8,6 +8,10 @@ import type {ApplicableType} from './'; import type ObjectTypeProperty from './ObjectTypeProperty'; +import ParameterizedClassDeclaration from "../declarations/ParameterizedClassDeclaration"; + +import {TypeParametersSymbol, TypeSymbol} from "../symbols"; + /** * # TypeParameterApplication * @@ -19,6 +23,29 @@ export default class TypeParameterApplication extends Type { *errors (validation: Validation, path: IdentifierPath, input: any): Generator { const {parent, typeInstances} = this; + + // explicitly set recorded type parameter in TypeParameter + if (parent && parent.impl && parent.impl[TypeSymbol] && input) { + const parentType : Type = parent.impl[TypeSymbol]; + + if (parentType instanceof ParameterizedClassDeclaration) { + const declTypeParameters = parentType.typeParameters; + const parentTypeParameters : Symbol = parent.impl[TypeParametersSymbol]; + const inputTypeParameters = input[parentTypeParameters]; + + if (declTypeParameters && inputTypeParameters + && declTypeParameters.length !== 0 + && declTypeParameters.length === this.typeInstances.length) { + declTypeParameters.forEach( (declTypeParameter : TypeParameter, index : number) => { + const inputTypeParameter : TypeParameter = inputTypeParameters[declTypeParameter.id]; + if (inputTypeParameter) { + inputTypeParameter.recorded = this.typeInstances[index]; + } + } ); + } + } + } + yield* parent.errors(validation, path, input, ...typeInstances); }