GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Control flow analysis for implicit any variables by ahejlsberg · Pull Request #11263 · microsoft/TypeScript

Conversation

@ahejlsberg

@ahejlsberg Anders Hejlsberg (ahejlsberg) commented Sep 29, 2016 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This PR introduces control flow analysis for let and var variables that have no type annotation and either no initial value or an initial value of null or undefined.

function f(cond: boolean) {
    let x;
    if (cond) {
        x = "hello";
        x;  // string
    }
    else {
        x = 123;
        x;  // number
    }
    return x;  // string | number
}

function g(cond: boolean) {
    let y = null;
    if (cond) {
        y = "hello";
    }
    return y;  // string | null
}

In the example above, x and y implicitly have declared types of any but control flow analysis can determine their actual types at every reference. Therefore, no errors are reported even when the example is compiled with --noImplicitAny.

Control flow analysis is unable to determine the actual types of implicit any variables when they are referenced in nested functions. In such cases, the variables will appear to have type any in the nested functions and errors will be reported for the references if --noImplicitAny is enabled.

function f(cond: boolean) {
    let x;  // Error: Variable 'x' implicitly has type 'any' in some locations where its type cannot be determined.
    if (cond) {
        x = "hello";
    }
    else {
        x = 123;
    }
    return x;  // type string | number
    function bar() {
        const y = x;  // Error: Variable 'x' implicitly has an 'any' type.
    }
}

In time it is possible that control flow analysis will be able to more accurately determine types of references to outer variables from nested functions in some cases, but given that nested functions are first class objects that can be arbitrarily passed around and called, it is effectively impossible to analyze all such scenarios.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tinganho, normalser, weswigham, mpawelski, geoffreak, aciccarello, lazdmx, and unional reacted with thumbs up emoji mpawelski reacted with hooray emoji benjamin21st reacted with confused emoji s-panferov and mpawelski reacted with heart emoji
All reactions
  • 👍 8 reactions
  • 🎉 1 reaction
  • 😕 1 reaction
  • ❤️ 2 reactions
Anders Hejlsberg (ahejlsberg) added 13 commits September 20, 2016 13:22
@ahejlsberg
Improve handling of ++ and -- in control flow analysis
38d16bb
@ahejlsberg
Accept new baselines
7df7bb2
@ahejlsberg
Use control flow analysis for let/var with no type annotation or init…
864ee1c
…ializer
@ahejlsberg
Unify anyType and autoType when checking for identical declarations
dda8aac
@ahejlsberg
Update tests
b14b24a
@ahejlsberg
Accept new baselines
c37b790
@ahejlsberg
Merge branch 'master' into controlFlowLetVar
bd19f1d
@ahejlsberg
Control flow typing for variables with null or undefined initializer
0621f7e
@ahejlsberg
Accept new baselines
52d2aa5
@ahejlsberg
Report error on both declaration and reference when CFA can't determi…
4479013
…ne type
@ahejlsberg
Accept new baselines
1c39cdc
@ahejlsberg
Add tests
d8ec857
@ahejlsberg
Accept new baselines
0739611
@msftclas Microsoft Contribution License Agreements (msftclas) added the cla-not-required label Sep 29, 2016
@ahejlsberg
Fix linting issue
6ae5337
sandersn
Nathan Shively-Sanders (sandersn) requested changes Sep 30, 2016
View reviewed changes

@sandersn Nathan Shively-Sanders (sandersn) left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

A couple of questions each about the change and the tests, plus a couple of ideas for the tests.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/compiler/checker.ts

function isAutoVariableInitializer(initializer: Expression) {
const expr = initializer && skipParentheses(initializer);
return !expr || expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>expr) === undefinedSymbol;

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Can this work for [] or {} as well?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Sep 30, 2016

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Yes, but they each need specialized strategies and I'll cover them in separate PRs.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

sandersn reacted with hooray emoji
All reactions
  • 🎉 1 reaction
Comment thread src/compiler/checker.ts
location = location.parent;
}
if (isExpression(location) && !isAssignmentTarget(location)) {
if (isPartOfExpression(location) && !isAssignmentTarget(location)) {

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

What does this change and why? I'm guessing that now we can get the type of a symbol given a child of the symbol's declaration, but I can't guess why.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Sep 30, 2016

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This is a bug that somehow got introduced during some refactoring for the new emitter. The old isExpression was renamed to isPartOfExpression and a new isExpression was introduced that doesn't look at the context (i.e. doesn't access the parent property).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread tests/cases/conformance/statements/for-inStatements/for-inStatements.ts
for (var x in new Date()) { }

var c, d, e;
var c: any, d: any, e: any;

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

why add any here? Is it to preserve the intent of the tests — does control flow pick a union type for these variables now?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Sep 30, 2016

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The test just needed some values of type any but they aren't actually meaningful once we start analyzing control flow.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread tests/cases/compiler/es5-asyncFunctionTryStatements.ts
// @noEmitHelpers: true
// @target: ES5
declare var x, y, z, a, b, c;
declare var x: any, y: any, z: any, a: any, b: any, c: any;

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I thought that auto typing didn't apply to ambient contexts. Why did you have to add any here?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Sep 30, 2016

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Yeah, you're right about that. But having them doesn't hurt and is a better indication of intent (i.e. we expressly don't want CFA here).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread tests/cases/conformance/Symbols/ES5SymbolProperty2.ts
//@target: ES5
module M {
var Symbol;
var Symbol: any;

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I don't understand why auto typing would apply here either

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ahejlsberg Anders Hejlsberg (ahejlsberg) Sep 30, 2016

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Same here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread tests/cases/compiler/controlFlowLetVar.ts

declare let cond: boolean;

function f1() {

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It would be nice if these functions had names that showed what they were testing.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread tests/cases/compiler/controlFlowLetVar.ts Outdated
if (cond) {
x = "hello";
}
const y = x;

@sandersn Nathan Shively-Sanders (sandersn) Sep 30, 2016

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

if you have if (cond) .. else then y: string | number, right? Might be nice to have a case for that.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Anders Hejlsberg (ahejlsberg) added 2 commits October 3, 2016 09:01
@ahejlsberg
Add comments to tests
6c2344e
@ahejlsberg
Update tests and baselines
dd63c46
@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Oct 3, 2016

Copy link
Copy Markdown
Member Author

Mohamed Hegazy (@mhegazy) Want to take a look?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

sandersn
Nathan Shively-Sanders (sandersn) approved these changes Oct 4, 2016
View reviewed changes
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Oct 6, 2016
Control flow analysis for array construction #11432
Merged
@ahejlsberg
Anders Hejlsberg (ahejlsberg) merged commit 44c475f into master Oct 6, 2016
@ahejlsberg
Anders Hejlsberg (ahejlsberg) deleted the controlFlowLetVar branch October 6, 2016 21:23
Mohamed Hegazy (mhegazy) added a commit to DefinitelyTyped/DefinitelyTyped that referenced this pull request Oct 7, 2016
@mhegazy
Fix fialing tests after microsoft/TypeScript#11263
a73da6a
Mohamed Hegazy (mhegazy) added a commit to DefinitelyTyped/DefinitelyTyped that referenced this pull request Oct 7, 2016
@mhegazy
Fix failing tests after microsoft/TypeScript#11263 (#11835)
13dec92
* Fix fialing tests after microsoft/TypeScript#11263

* Fix more failures
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Nov 2, 2016
No error when using uninitialized value in noImplicitAny & strictNullChecks mode #12002
Closed
@jods4

jods (jods4) commented Nov 8, 2016

Copy link
Copy Markdown

Anders Hejlsberg (@ahejlsberg)
Reading the descriptions, I have the impression that the following code example won't be a --noImplicitAny error. Am I wrong?

function f(x: number[]): void { ... }

let x = [];  // x: any[]
x.push(3); // x: number[]
f(x); // x is ok according to control flow, so no --noImplicitAny error?
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mhegazy

Mohamed Hegazy (mhegazy) commented Nov 8, 2016

Copy link
Copy Markdown
Contributor

Am I wrong?

You are correct. this is not flagged as an implicit any error.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jods4

jods (jods4) commented Nov 8, 2016

Copy link
Copy Markdown

Mohamed Hegazy (@mhegazy) this is not safe, though.
Consider that f() might use the x value later (through capture or saving into a global variable) and the code might arbitrarily change x.

function f(x: number[]): void {
  setImmediate(function () { 
    console.log(x.reduce((a, b) => a * b, 1));
  });
}

let x = [];
x.push(3);
f(x);
x.push("I am not a number"); // ok, now x: (string|number)[]
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn

Nathan Shively-Sanders (sandersn) commented Nov 9, 2016

Copy link
Copy Markdown
Member

Typescript is already unsafe in this way. You can also write

let sns: (string | number)[];
f(sns);
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn

Nathan Shively-Sanders (sandersn) commented Nov 9, 2016

Copy link
Copy Markdown
Member

Never mind, I was completely wrong. It doesn't work.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jods4

jods (jods4) commented Nov 9, 2016

Copy link
Copy Markdown

Nathan Shively-Sanders (@sandersn)
Yeah, I like that this enables more errors to be caught when migrating (untyped) JS code.
I like that you now can drop more type declarations for local variables as long as you use them correctly.

But I think many people like TS for being (for the most part) statically type-safe and use noImplicitAny to guarantee that no dynamically typed code slips through, i.e. as a way to enforce type safety.
Currently, this PR weakens the guarantees provided by noImplicitAny and allows some unsafe code that was not possible before.

Thus, I would suggest that you limit what is allowed by this feature or (harder) only allow cases that are provably correct (e.g. determine that the dynamic type never changes to an incompatible type).

I can at least imagine the following operations to be potentially unsafe: saving the value in an object, an array or a variable in an outer scope; assigning the value to a setter; passing the value to a function; capturing the value in a closure; calling a member of the value; new-ing the value.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mhegazy

Mohamed Hegazy (mhegazy) commented Nov 10, 2016

Copy link
Copy Markdown
Contributor

jods (@jods4), the compiler today does not guard against side effects in multiple places. it is not specific to noImplicitAny checks. for instance:

function f(x: { a: number }) {
    setTimeout(() => {
        x.a.toFixed();
    }, 10);
 }

var x: { kind: "number", a: number } | { kind: "string", a: string };

if (x.kind === "number") { 
    f(x);
}

x.a = "string"; // potentially unsafe, as x has already been captured.

Arrays are just like objects with properties when it comes to narrowing. I do understand your concern, but really this issue is a general design limitation and not specific to this change.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jods4

jods (jods4) commented Nov 10, 2016

Copy link
Copy Markdown

Mohamed Hegazy (@mhegazy)
Yes, I agree.
That's why I said TS is for the most part statically type-safe, which is already a lot 😃

That said, having safety holes doesn't necessarily mean we should add new ones. I guess the question is: do benefits outweight the drawbacks?

Your example is a lot more convoluted than the simple example I gave, which was just:

let x = [];
x.push(3);
f(x);
x.push("NaN");

I guess what annoys me most here is that this is unsafe because I use x in a dynamic way, without declaring my intent, under noImplicitAny. I think the essence of noImplicitAny is to forbid errors from unwanted dynamic code.

My opinion:

  1. This is great for migrating existing JS code and I wouldn't change anything in this context.
  2. I believe people who use noImplicitAny have little use for that feature. Typically those people want maximum safety and try to have everything typed. In this context and with all the inference we already have, you seldom have the need for this shortcut. In my company we build web apps exclusively in this model and I can't say I ever had feature envy for that.

To maximize both safety and value, I would suggest that under noImplicitAny, the dynamic type of locals cannot be changed. I.e.:

let x;  // this is not an error yet.
x = 4;  // so x: number at this point
console.log(x.toFixed());  // sure
x += 5;  // and so on...

let y;  // same thing
y = 4;  // so y: number
y = "Four";  // still an error when --noImplicitAny. Can't use dynamic types without explicit declaration.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Nov 16, 2016
Compiler should allow assignment of new property in empty object #11839
Closed
@zhuravlik Anton (zhuravlik) mentioned this pull request Jan 31, 2017
Control flow analysis for implicit any and array initialization works only when --noImplicitAny is on #13779
Closed
@HerringtonDarkholme Herrington Darkholme (HerringtonDarkholme) mentioned this pull request Sep 22, 2017
Config noImplicitAny seems to be not working since typescript 2.1 #18679
Closed
@microsoft Microsoft (microsoft) locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@sandersn Nathan Shively-Sanders (sandersn) sandersn approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@ahejlsberg @jods4 @mhegazy @sandersn @msftclas
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.