-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Closed
Labels
Working as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Description
TypeScript Version: 2.5.2
Code
let array = [];
array.push(1); // tsc error: 1 is not never;
console.log(array);
Compiler Options
{
"target": "es2017",
"module": "commonjs",
"strict": true,
"noImplicitAny": false,
"noImplicitThis": false,
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
Expected behavior
» tsc⏎
# a pause of nothingness
Actual behavior
» tsc
2 array.push(1); // tsc error: {} is not never;
~
example.ts(2,12): error TS2345: Argument of type '1' is not assignable to parameter of type 'never'.
Potential causes
After some testing, I did pinpoint the culprit behind this problem in my compiler options:
"noImplicitAny": false
It seems that setting noImplicitAny
to true
does not suffer from this issue but for some reason false
seems to result in the opposite effect of what is expected.
So in my opinion, somewhere in related to parsing this particular compiler option there might be some assumption related to the various possible values for the tri-states of noImplicitAny
.
Sample Project:
typescript-empty-array-issue.zip
» yarn && yarn build
Thanks!
Metadata
Metadata
Assignees
Labels
Working as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
[-]Question: Should `let array = []` really mean we want a `never[]` instead of `any[]` or is it just me?[/-][+]Question: Should `let array = []` really mean we want a `never[]` instead of `any[]` or is it just my compiler options?[/+]RyanCavanaugh commentedon Sep 22, 2017
This is caused by the combination of
strict
andnoImplicitAny: false
. In general we expect that ifstrict
is on,noImplicitAny
is also on; this particular set of settings will expose some odd behavior. If you had both on, you'd see an error about the[]
being implicitlyany[]
; if both were off; we'd use control flow analysis and treat the array as anumber[]
after thepush(1);
.The particular combination of settings means that we don't allow ourselves to use control flow analysis or allow the array to be implicitly
any[]
, sonever[]
is the only remaining allowable option.I'd recommend turning off
strict
if you're not going to havenoImplicitAny
on.SMotaal commentedon Sep 25, 2017
I got a chance to try the various combinations and it is consistent. Is there a way to make such combinations — ones that would have predictable but maybe not intuitive side effects from a user's perspective — standout or be flagged to make them realize that.
I am going with an assumption that array defaulting to never is probably not a desired type-checking configuration for most users.
Maybe if the type-checker description for something is not never may trigger reference the options and indicate the face that those two options together are a potential mistake.
Thanks :)
SMotaal commentedon Sep 29, 2017
@RyanCavanaugh would it be practical to point out in some diagnostic output that those two options together have such implications?
RyanCavanaugh commentedon Oct 3, 2017
I'm not sure that's a long-term tractable solution. There are about 80 flags so even conservatively that's 80 * 79 = 6,300 flag combinations which are potential traps to think about. As far as I know you're the first person to notice this, so on balance it's probably not worth handling. We can reconsider if more people find odd combinations of flags that have bad results.
SMotaal commentedon Oct 3, 2017
I just really hope there aren't actually 6,320 traps out there 👍
RyanCavanaugh commentedon Oct 3, 2017
Me too 😅
bcherny commentedon Feb 11, 2018
To follow up on this issue, with TSC 2.7.1 (
strict
andnoImplicitAny
enabled) the behavior is not what I would expect:a
is implicitly anany[]
- why doesn't this throw? I assume this is by design, is there a good discussion somewhere?Playground link
RyanCavanaugh commentedon Feb 12, 2018
@bcherny it's not
any[]
, it's dynamically typed according to what you do with it. If you actually try to induce an unsoundany
observation, you'll see an error.bcherny commentedon Feb 12, 2018
@RyanCavanaugh Right. I guess to rephrase, it looks like I can keep widening
a
's type ad nauseum after I initially constrain it by pushing1
. Ie.I still get safety when reading a member of
a
, because the union is inferred correctly. It's just surprising that I can keep widening the type. Why did you choose to design it that way, instead of erroring onlet a = []
? Is it just such a common JS pattern that it would be too verbose to have to explicitly typea
upfront?RyanCavanaugh commentedon Feb 12, 2018
It's an improvement on the existing behavior because it gives fewer errors but catches the same type mismatches.
Intuitively, this code
should not behave any differently from
since we know they're equivalent.
bcherny commentedon Feb 12, 2018
I see. So when
a
is contextually typed, pushing some typeT
is semantically similar to initializing with it, so TSC treats them the same. And if the push happens somewhere else (wherea
is no longer contextually typed), then you have to make explicit whata
's type is.That makes a lot of sense, thanks @RyanCavanaugh!