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

Index signature is assignable to weak type whose properties don't match the signature type #27144

Description

From https://stackoverflow.com/q/52368008 . Based on #16047 (comment), it appears that Nathan Shively-Sanders (@sandersn) may have thought about the rule, but it still makes no sense to me and no rationale is stated.

TypeScript Version: master (394ee31)

Search Terms: weak type index signature

Code

interface Foo {
    a?: string;
}
interface Bar {
    [n: string]: number;
}
declare let b: Bar;
// No error; expected "Type 'Bar' has no properties in common with type 'Foo'."
let a: Foo = b;

Expected behavior: Error: "Type 'Bar' has no properties in common with type 'Foo'."

Actual behavior: No error.

Playground Link: link

Related Issues: Possibly #9900

Activity

  1. sandersn commented on Sep 17, 2018

    @sandersn
    Member

    I think of index signatures as asserting that a type may contain every property, in which case the weak type check is trivially satisfied. But if you think of them as having no defined properties, similar to {}, you would still hit the exception for empty source types that is part of the weak type check. That is, an empty object type is always assignable to a weak type, in order to enable the pattern let options: Weak = {}.

  2. mattmccutchen commented on Sep 17, 2018

    @mattmccutchen
    ContributorAuthor

    Thanks for the prompt response!

    I think of index signatures as asserting that a type may contain every property, in which case the weak type check is trivially satisfied.

    That interpretation of an index signature doesn't seem consistent with the fact that TypeScript doesn't check that the return type of the index signature is compatible with the type of a. I know that the weak type check itself is never concerned with property types, but the way that the weak type check interprets property names vs. index signatures should be consistent with the way that property type compatibility does.

    More practically, we've just heard from a user who mistakenly assigned a type with only an index signature to a weak type. Are there reasonable cases in which such an assignment is intentional?

  3. sandersn commented on Sep 17, 2018

    @sandersn
    Member

    Ah, I didn't see the types. :) I have the weak type implementation so much in my head that I just looked at properties.

    So it sounds like the correct check for index signatures is to make sure that the signature type is assignable to the union of the weak type's property types.

  4. changed the title [-]Type with only index signature is assignable to weak type[/-] [+]Index signature is assignable to weak type whose properties don't match the signature type[/+] on Sep 17, 2018
  5. sandersn commented on Sep 17, 2018

    @sandersn
    Member

    Well, here's what it looks like, inside the main weak type check:

    const index = getIndexInfoOfType(source, IndexKind.String);
    if (index && !isRelatedTo(getIntersectionType([index.type, ...map(getPropertiesOfType(source), getTypeOfSymbol)]), getUnionType(map(getPropertiesOfType(target), getTypeOfSymbol)))) {
        if (reportErrors) {
    	// NB Should have a specific error here
    	reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target));
        }
        return Ternary.False;
    }

    This is a related check to the current weak type check, though. It's not the same thing. It says that even though there is a value that satisfies both types, that value is {}, which is not a useful value. Instead, we assume that both types have a value with at least one property, in which case no value could satisfy both types.

    Daniel Rosenwasser (@DanielRosenwasser) what do you think about this check? I need to run it on the user tests to see if it finds a bunch of errors, but nothing has turned up in suite 0 tests.

    Edit: Doesn't find any errors in the user test suite, although it's mostly Javascript, so not too many weak types there.

    Edit: Nope, nothing in RWC either.

  6. mattmccutchen commented on Sep 17, 2018

    @mattmccutchen
    ContributorAuthor

    I didn't know a "Bug" could be "In Discussion". 😄

    For that matter, if we interpret an index signature as "there might exist a property of any name", why do we ever allow a type with an index signature to be assignable to a type with an incompatible optional property, regardless of whether the target type is weak? Are there real-world cases in which the error would be undesirable? Example:

    interface Foo {
        a: number;
        b?: string;
    }
    interface Bar {
        a: number
        [n: string]: number;
    }
    let b: Bar = {a: 42, b: 43};
    let f: Foo = b;  // OK; expect error?
  7. sandersn commented on Sep 17, 2018

    @sandersn
    Member

    Matt McCutchen (@mattmccutchen) I think you have found the real issue. I'm not sure why it works this why. Maybe the spec has a justification?

  8. added a commit that references this issue on Oct 7, 2018
    3b5caa3
  9. mattmccutchen commented on Oct 7, 2018

    @mattmccutchen
    ContributorAuthor

    I wanted to see how many TypeScript PRs I could submit in a day, so I went ahead and submitted #27591 to check a source index signature (or rest type if the source is a tuple type) against target optional properties. There was one good (IMO) break in the compiler itself. I guess you can see what happens with the "real-world code" suite.

  10. jcalz commented on May 16, 2022

    @jcalz
    Contributor

    Relevant SO question where someone smacks into this.

  11. umenosuke commented on Nov 20, 2024

    @umenosuke

    I think this issue could cause potential bugs in the code.
    Why has this PR been closed?

    If the PR was closed because existing code with potential issues fails to transpile,

    Would it be possible to consider implementing individual checks through options like StrictXXX?
    What are your thoughts on this?

  12. umenosuke commented on Nov 20, 2024

    @umenosuke

    By the way, I had trouble with this

    Playground Link: link

  13. jthemphill commented on Apr 23, 2025

    @jthemphill

    umenosuke:

    By the way, I had trouble with this

    In your example, that is because every property in file is optional. Because every property in file is optional, then any object is technically a file. If you make any property of file non-optional then you'll see type errors. Why not make size non-optional? Playground Link

  14. umenosuke commented on Jun 3, 2026

    @umenosuke

    Ah, I see. In the previous example, that is indeed expected behavior.
    I understand now that it is perfectly fine to accept an argument when the optional property is missing.

    However, as shown in the simplified example below, I believe it is problematic that it accepts types that should normally be rejected when using an index signature.

    I feel that this should at least be caught (or rejected) when the strict option is enabled.

    type A = {
        A_aaaa?: string,
    };
    
    const b: { [name: string]: boolean } = {
        "A_aaaa": true,
    };
    
    function funcX(f: A | undefined): {
        aaaa: string,
    } {
        return {
            aaaa: f?.A_aaaa ?? "default name",
        };
    }
    
    {
        const f1 = funcX(b); // ❌ No compile error, but outputs: { aaaa: true }
        console.log("❌️", f1);
    
        // It is equivalent to asserting the type like this.
        const f2 = funcX(b as unknown as A);
        console.log("❌️", f2);
    }
    

    Playground Link: link

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

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions