Skip to content

Regression in type inference from 2.5 -> 2.6 #19668

Closed

Description

@phiresky

TypeScript Version: 2.6.0-rc, 2.7.0-dev.20171101

Code Runnable Gist

import * as s from "@hediet/typed-sql";
import { Pool } from "pg";

const db = new s.DbConnection(new s.PostgreQueryService(new Pool()));

const customers = s.table(
    "customers",
    { name: s.tText, country: s.tText },
    { id: s.tInteger }
);

const orders = s.table(
    "orders",
    { customerId: customers.id.type, orderDate: s.tText },
    { id: s.tInteger }
);

async function test() {
    return await db.exec(
        s
            .from(customers)
            .where({ country: "US" })
            .leftJoin(orders)
            .on({ id: orders.id })
            .select(orders.$all)
    );
}
{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "strict": true
    }
}

Expected behavior:

Correctly inferred type of test() (Typescript 2.5.3):

function test(): Promise<{
    customerId: number;
    orderDate: string;
    id: number;
}[]>

Actual behavior:

2.6.0-rc:

function test(): Promise<void>

2.7.0-dev.20171029:

function test(): Promise<{
    [x: string]: any;
}[]>

No type errors are shown in either version.

Activity

  1. ahejlsberg commented on Nov 1, 2017

    @ahejlsberg
    Member

    I'm getting lots of errors when I compile the example (after installing the referenced npm modules). In particular, many of the errors relate to the fix implemented in #17922. It appears the @hediet/typed-sql package attempts to access type parameters in a base class expression. This never worked correctly and is now an error. See #17829 (comment) for how to implement this pattern correctly.

  2. phiresky commented on Nov 2, 2017

    @phiresky
    Author

    Right, compiling with tsc does indeed show type errors in the library, in VSCode it does not show them. Henning Dieterichs (@hediet), can you look into it?

  3. zsparal commented on Nov 2, 2017

    @zsparal

    Is there an easy way to properly type generic classes that extend from a class expression? The use-case is Immutable.js's Record class:

    Common:

    interface NodeProps<N> {
      value: N;
      edges: List<string>;
    }

    Here's what I want to do:

    export class Node<N> extends Record<NodeProps<N>>(defaultNode) {
      setValue(value: N): Node<N> {
        return this.set("value", value as never);
      }
    }

    Unfortunately this gives me Node.value: any so here's what I came up with as a workaround:

    interface NodeMethods<N> {
      setValue(value: N): Node<N>;
    }
    
    class NodeBase<N> extends Record<NodeProps<any>>(defaultNode) implements NodeMethods<N> {
      setValue(value: N): Node<N> {
        return this.set("value", value);
      }
    }
    
    export type Node<N> = Readonly<NodeProps<N>> & NodeMethods<N> & Record<NodeProps<N>>;
    export function Node<N>(values: Partial<NodeProps<N>>): Node<N> {
      return new NodeBase(values);
    }

    Is there a better way to go about this?

    Edit: Sorry if this belongs to #17829

  4. aluanhaddad commented on Nov 4, 2017

    @aluanhaddad
    Contributor

    Record<NodeProps<N>>(defaultNode) does not make sense since Record requires 2 type parameters, a class cannot extend a type, and a type cannot be called. Are you trying to write a mixin?

  5. zsparal commented on Nov 5, 2017

    @zsparal

    I'm using version 4.0.0-rc.9 of immutable which only requires a single type parameter (TProps) for defining a Record. The two relevant type definitions:

    export function Factory<TProps extends Object>(values?: Partial<TProps> | Iterable<[string, any]>): Record<TProps> & Readonly<TProps>;
    export function Record<TProps>(defaultValues: TProps, name?: string): Record.Factory<TProps>;

    Extending from Record is documented in the docs and it works correctly except when using a generic interface as TProps . This used to work incorrectly, using any instead of the proper type parameter before 2.6 and fails with a compile-time error after 2.6.

    Here's a minimal repro that (unlike Record) happens to work correctly with 2.5 but still fails with 2.6:

    export function MyRecord<T>() {
      return class {
        value: T;
      };
    }
    
    export class Foo<T> extends MyRecord<T>() {}
  6. aluanhaddad commented on Nov 5, 2017

    @aluanhaddad
    Contributor

    Ah, I see, I though you were referencing TypeScript's built in Record<K, T> mapped type. Since as mentioned was never correct, you can either write

    export function MyRecord<T = any>() {
      return class {
        value: T;
      };
    }
    
    export class Foo<T> extends MyRecord() {}

    to obtain an approximation of the old behavior.

    Or write

    export function MyRecord() {
      return class<T> {
        value: T;
      };
    }
    
    export class Foo<T> extends MyRecord()<T> {}
    const foo = new Foo<string>();
    foo.value.toLocaleLowerCase();

    to pass the type parameter to the expression that results from calling the class factory.

  7. typescript-bot commented on Nov 20, 2017

    @typescript-bot
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  8. locked and limited conversation to collaborators on Jun 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions