Skip to content

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.

@microsoft microsoft locked as resolved and limited conversation to collaborators Oct 16, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.