facebook / flow

Adds static typing to JavaScript to improve developer productivity and code quality.
https://flow.org/
MIT License
22.08k stars 1.86k forks source link

Generic return type not respected #3348

Closed mpfau closed 7 years ago

mpfau commented 7 years ago

The following example illustrates how generic return type information is lost:

// @flow
type Vehicle = {
    name:string;
}

type ClassReference<T> = {
    id: string;
}

function loadIt<T>(ref: ClassReference<T>):T {
    return ({name: 'Ford'}:any)
}

let VehicleClassRef : ClassReference<Vehicle> = {id: 'vehicle'}

let loaded = loadIt(VehicleClassRef)
loaded.nonExisting

let loadedAsVehicle: Vehicle = loadIt(VehicleClassRef)
loadedAsVehicle.nonExisting

An error should occur at loaded.nonExisting. However, the error only occurs at the last line (where the variable has been casted explicitely).

jesseschalken commented 7 years ago

Types declared with type are structural, not nominal, so compatibility between them is considered on the basis of their properties. Since you didn't use T in any of the properties of ClassReference, T has no effect on the typing, and the type of loadIt is effectively loadIt<T>(ref: {id: string}): T which is a function for which you can treat the return type as anything you want (which is a little nonsensical, but a function that always throws would technically fit that type).

Classes are nominal, so if you want T to have an effect on typing even though it doesn't occur in any properties or methods, use a class instead. This works, for example:

type Vehicle = {
    name:string;
}

class ClassReference<T> {
    constructor(id: string) {}
}

function loadIt<T>(ref: ClassReference<T>):T {
    return ({name: 'Ford'}:any)
}

let VehicleClassRef : ClassReference<Vehicle> = new ClassReference('vehicle')

let loaded = loadIt(VehicleClassRef)
loaded.nonExisting

let loadedAsVehicle: Vehicle = loadIt(VehicleClassRef)
loadedAsVehicle.nonExisting
mpfau commented 7 years ago

@jesseschalken Thanks for these detailed insights! We just switched to classes and everything works as expected.