Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Narrow on element access of known property #10565

Closed
wants to merge 7 commits into from

Conversation

sandersn
Copy link
Member

Fixes #10530

Narrowing now happens for x['knownProperty'] the same way that it does for x.knownProperty

@sandersn
Copy link
Member Author

@RyanCavanaugh @ahejlsberg do you want to take a look?

expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
if (expr.kind === SyntaxKind.ElementAccessExpression) {
const argument = (expr as ElementAccessExpression).argumentExpression;
return argument.kind === SyntaxKind.StringLiteral || argument.kind === SyntaxKind.NumericLiteral || argument.kind === SyntaxKind.EnumMember;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the expr.expression should be, an identifier, this, a PopertyAccessExpression or ElementAccessExpression , so we should recursively call the function on the expression.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also add tests to multiple levels of elementAccess. a[0][1]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, multiple levels of property access don't work either. So I think this function should actually be even simpler until we fix that bug.

interface Square {
  sub: { kind: "square; };
  size: number;
}
// etc ...
function area(s: Shape) {
    switch(s.sub.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}

@sandersn
Copy link
Member Author

Nested element accesses are now supported.

@sandersn
Copy link
Member Author

@mhegazy you started reviewing this change. Can you look at the new commits so it can go in?

@sandersn
Copy link
Member Author

sandersn commented Sep 1, 2016

I ran a performance benchmark on the code compared to master. The standout difference was that Monaco (the online component that powers the playground) has a 6.85% slowdown in the checker, or 0.41 second. TFS has a small slowdown in the checker but it's not statistically significant.

I'm trying to get github formatting to co-operate so I can show the actual table but haven't had any luck yet.

@sandersn
Copy link
Member Author

sandersn commented Sep 1, 2016

As @mhegazy suggested, I tried the benchmark again, this time only narrowing string element accesses, not number accesses. The results are about the same.

@mhegazy
Copy link
Contributor

mhegazy commented May 22, 2017

given the performance penalty vs. utility of this change, we will not be able to take it.

@mhegazy mhegazy closed this May 22, 2017
@mhegazy mhegazy deleted the narrow-on-element-access-of-known-property branch November 2, 2017 21:03
@kachkaev
Copy link

kachkaev commented Mar 27, 2018

Would it be possible to revise the decision on this PR 1.5 years since the original implementation? Will the performance impact be so critical now too?

When a property has spaces or other no-standard characters, it is impossible to either define it or read its value via x.knownProperty. This makes it impossible to validate dictionaries configs., e.g.:

const config = {
  param: 42,
  "param 2": 43,
}

console.log(config.parammmmm); // <--- typo caught by the compiler
console.log(config["parammmmm 2"]); // <--- typo not caught

@mhegazy
Copy link
Contributor

mhegazy commented Mar 27, 2018

i do not think the error you listed above has do with this PR. The issue here is that it is legal to index into any property using a string/numeric name. the result is an implicit any. This should be an error under --noImplicitAny.

@kachkaev
Copy link

kachkaev commented Apr 5, 2018

I'm not sure that --noImplicitAny would work for me here, because I'll have to add type annotation when defining config and the code will become too verbose. --noImplicitAny also affects function argument definition, which I'd prefer to avoid.

@m1gu3l
Copy link
Contributor

m1gu3l commented Jun 14, 2018

could this perhaps land behind a flag? or maybe just for numeric properties accessed via brackets?

interface X {
    0: "xx",
    1: number
}

interface Y {
    0: "yy",
    1: string
}

type A = ["aa", number];
type B = ["bb", string];

type Z = X | Y;

type C = A | B;

function check(z: Z, c: C) {
    z[0] // fine, typescript sees "xx" | "yy"
    switch (z[0]) {
        case "xx":
            z[1] // should be number, typescript sees string | number
            break;
        case "yy":
            z[1] // should be string, typescript sees string | number
            break;
    }
    c[0] // fine, typescript sees "xx" | "yy"
    switch (c[0]) {
        case "aa":
            c[1] // should be number, typescript sees string | number
            break;
        case "bb":
            c[1] // should be string, typescript sees string | number
            break;
    }
}

@sandersn
Copy link
Member Author

I reconstituted the PR and found (1) a probably performance bug that it introduced in isMatchingReference and friends (2) no significant difference in performance after fixing the bug. I'll open a new PR with the details and some tests.

@BalaM314
Copy link

Can we have this behind a flag? I've had to add dozens of non-null assertions to fix this and would gladly take the 6.5% performance hit.

@thelinuxlich
Copy link

I agree, the current behavior is unintuitive and should be fixed.

@phaseOne
Copy link

For what it's worth, I've verified that noImplicitAny/strict addresses the case in #10565 (comment).

For my use case, strict is fine to enable. The only reason I'm making this post is that it's unintuitive that noImplicitAny controls type checking for property accessors (bracket notation) in common cases like the aforementioned, so hopefully, this post enables the algorithm to help other users find the workaround and latest news.

It's purported that #10530 is tracking the issue and #48335 is tracking new documentation for the current behavior and workarounds.

@Zzzen
Copy link
Contributor

Zzzen commented Nov 4, 2023

Would it be possible for us to simply rebase this branch and rerun the benchmarks to get a clearer picture of the real-world performance?

@DamianGlowala
Copy link

@sandersn, what do you think about the above question? Could we give this another try, please?

@RyanCavanaugh
Copy link
Member

One does not "simply" rebase a 7-year-old PR 😅

The example in the linked issue, and the example in this comment #10565 (comment), both now work. Can you clarify what you're looking for?

@DamianGlowala
Copy link

Now as I am reading the purpose of this PR again, I certainly commented on the wrong one. I was trying to figure out why TS doesn't narrow down the type in this specific scenario.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet