镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Structural Subtyping with Control flow based type analysis #10065

Description

I like --strictNullChecks options.
But this options is very hard to apply existing project.
I want to remove optional operator from interface.
TypeScript 2.0.0 can it! but assignment operation is not under control flow context.
I think we need it.

for example.

declare var fs: { readFileSync(filePath: string, encoding: string): string; };

interface Config {
    filePath?: string;
    verbose?: boolean;
}

// add for --strictNullChecks support. I want use this interface wider.
interface ConfigFixed {
    filePath: string;
    verbose: boolean;
}

function foo(config: Config = {}) {
    config.filePath = config.filePath || "settings.json";
    config.verbose = config.verbose || false;

    // config.filePath is string. ts 2.0.0 is super cool!
    config.filePath;
    // config.verbose is boolean. ts 2.0.0 is very smart!
    config.verbose;

    // but an error occured.
    // test.ts(16,9): error TS2345: Argument of type 'Config' is not assignable to parameter of type 'ConfigFixed'.
    //   Types of property 'filePath' are incompatible.
    //     Type 'string | undefined' is not assignable to type 'string'.
    //       Type 'undefined' is not assignable to type 'string'.
    bar(config);
}

function bar(config: ConfigFixed /* replace from Config since TypeScript 2.0.0 */) {
    let data = fs.readFileSync(config.filePath, "utf8");
    config.verbose && console.log(data);
}

or

function withDefaultValue(config: Config = {}): ConfigFixed {
    config.filePath = config.filePath || "settings.json";
    config.verbose = config.verbose || false;

    return config;
}

function buzz(config: Config = {}) {
    // I can add below line without constraint.
    config = withDefaultValue(config);
    // I want to `config` value be `ConfigFixed` type by Control flow based type analysis.
    config.filePath;
}

Off topic.

function withDefaultValue2(config: Config = {}): ConfigFixed {
    // valid. cool!
    return Object.assign(config, {
        filePath: "settings.json",
        verbose: false,
    });
}

Activity

  1. added
    Design LimitationConstraints of the existing architecture prevent this from being fixed
    and removed on Nov 15, 2016
  2. RyanCavanaugh commented on Nov 15, 2016

    @RyanCavanaugh
    Member

    Continuing to think about this but it'd be very expensive -- we'd be resynthesizing a type for config after every assignment. It's more likely we'll figure out some kind of scoped assertion syntax that lets you specify "from here on out, config is now of type ConfigFixed"

  3. zpdDG4gta8XKpMCd commented on Dec 14, 2016

    @zpdDG4gta8XKpMCd

    migrated from #12919

    allow an object narrowed to a certain interface be used where such interface is expected

    interface A { value: number | string };
    interface B { value: number; }
    function takeOnlyB(value: B): void {}
    const data: A = { value: 'hey' };
    data.value = 1; // <-- from this point on we know for sure that value is number
    takeOnlyB(data);  // <-- problem, despite effectively being of type `B` data still can't be used
    
    // expected:  takeOnlyB should accept data because it's been asserted to comply with `B`
    Argument of type 'A' is not assignable to parameter of type 'B'.
      Types of property 'value' are incompatible.
        Type 'string | number' is not assignable to type 'number'.
          Type 'string' is not assignable to type 'number'.
    
  4. zpdDG4gta8XKpMCd commented on Dec 14, 2016

    @zpdDG4gta8XKpMCd

    @RyanCavanaugh

    from here on out, config is now of type ConfigFixed

    duplicate of https://github.com/Microsoft/TypeScript/issues/8545

  5. zpdDG4gta8XKpMCd commented on Dec 14, 2016

    @zpdDG4gta8XKpMCd

    from here on out, config is now of type ConfigFixed

    would also be an asnwer to mixins or partial classes

    https://github.com/Microsoft/TypeScript/issues/563#issuecomment-218841071

  6. RyanCavanaugh commented on Sep 7, 2017

    @RyanCavanaugh
    Member

    A casual observation is that allowing this would be unsound because someone could hold a reference to the guarded object, then have the object mutate at a later point during execution to have an incorrectly-typed property (from the perspective of the callee). We're in general very unsound in this regard already so it's not really a reason to not do the feature... but people are always telling me TypeScript is Very Bad because it's unsound so I figured it was worth mentioning in the context of this suggestion 🙄

  7. aluanhaddad commented on Sep 8, 2017

    @aluanhaddad
    Contributor

    @RyanCavanaugh

    people are always telling me TypeScript is Very Bad because it's unsound so I figured it was worth mentioning in the context of this suggestion

    Unsound? Yes.
    Very bad? Not on your life 😁.

    In all seriousness, it works for people, lets us get our jobs done, and follows its stated goals.

    The rough edges, which are getting smoother every day, come more from things like module resolution, declaration acquisition, and bad introductory materials (see Angular tutorials), that give developers the wrong impressions about the purpose of the language. Even the Wikipedia page states that TypeScript brings class-based object orientation to JavaScript, so there's a lot of incorrect information out there and when people want to believe something, they find a way...

  8. jkillian commented on Sep 19, 2017

    @jkillian

    Ran into essentially the same scenario today with narrowing union types. Key difference here is that in my case there's no assignment happening, only type guards. If this is a different enough, happy to break it out into its own issue if necessary.

    type Union = A | B;
    type A = { type: "a", isA: boolean };
    type B = { type: "b", isB: boolean };
    
    interface Container { union: Union };
    interface ContainerNarrowed extends Container {
        union: A;
    }
    
    declare const container: Container;
    
    if (container.union.type === "a") {
        // this is valid, so TS knows that `union` is an `A`
        container.union.isA;
    
        // compiler error:
        // Type 'Container' is not assignable to type 'ContainerNarrowed'.
        //   Types of property 'union' are incompatible.
        //     Type 'Union' is not assignable to type 'A'.
        const containerNarrowed: ContainerNarrowed = container;
        // it seems as if `container` should satisfy `ContainerNarrowed` here?
    }
  9. locked and limited conversation to collaborators on Jun 19, 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

    Design LimitationConstraints of the existing architecture prevent this from being fixedSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions