Skip to content

Confusing 'never' return type when appending an empty array #9976

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

Closed
rtpg opened this issue Jul 27, 2016 · 13 comments
Closed

Confusing 'never' return type when appending an empty array #9976

rtpg opened this issue Jul 27, 2016 · 13 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@rtpg
Copy link

rtpg commented Jul 27, 2016

TypeScript Version: 2.0.0

Code

        var x = [];
        x.push(1);

Expected behavior:
Accept this, and have the type inferencer postulate x:number[]. Alternatively, an error message along the lines of "cannot assign number to parameter {}" (because type inferencer could also assume x:undefined[]).

Actual behavior:
(with strictNullCheck)

error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.

This might be because of how never is documented as the "bottom" type (for functions that never return), but since the code isn't using the result expression, this is pretty confusing. Is this because the assumed type of x is never[]?

I tried looking for related issues, I feel like it's slightly related to empty tuple discussions, but not 100% sure.

@Arnavion
Copy link
Contributor

#8944

@basarat
Copy link
Contributor

basarat commented Aug 5, 2016

Maybe there Is there something like noImplicitAny that will make sense for any[] instead? 🌹

@kitsonk
Copy link
Contributor

kitsonk commented Aug 5, 2016

@basarat it is called not setting --strictNullCheck 😉. I would say it is desirable behaviour that if you have said that you want to make sure you don't have null values, that the type system becomes more strict. You can of course be explicit about it (x: any[] = []) and it works.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 5, 2016
@RyanCavanaugh
Copy link
Member

#8944 covers why this is done

@robertpenner
Copy link

robertpenner commented May 25, 2017

@RyanCavanaugh I've read through the related discussions and now I get the logic of why let a = [] is inferred as never[]. But prior to that, the compiler error message is not very helpful; it's quite difficult to understand what is going on and how to resolve the error.

@108adams
Copy link

My question: why never in this:

let rows: Element[] = [].concat(generateElements());

but not in this:

let rows: Element[] = [];
rows.concat(generateElements());

@EliRobinson
Copy link

EliRobinson commented Mar 28, 2018

@108adams - Your code example doesn't actually concat the rows. As concat returns a new array. By doing rows.concat(generateElements()); you aren't actually updating rows to contain generateElements(). Might be why this doesn't throw the error?

You'd need to say:

let rows: Element[] = [];

rows = rows.concat(generateElements());

@TAGC
Copy link

TAGC commented May 18, 2018

I love how Typescript is still giving me an error:

"TS2345: Argument of type '{ style: { position: string; x: number; y: number; height: any; width: any; }; }' is not assignable to parameter of type 'never'."

When I've clearly annotated the type:

export function getTilesInSection(
  x: number,
  y: number,
  sectionWidth: number,
  sectionHeight: number,
  tileSize: number
): GridTileContainer[] {
  const halfSectionWidth = sectionWidth / 2;
  const halfSectionHeight = sectionHeight / 2;
  const halfTileSize = tileSize / 2;
  const x1 = x - halfSectionWidth;
  const x2 = x + halfSectionWidth;
  const y1 = y - halfSectionHeight;
  const y2 = y + halfSectionHeight;

  const xMidpoints = multiplesInRange(tileSize, x1, x2);
  const yMidpoints = multiplesInRange(tileSize, y1, y2);

  let containers: GridTileContainer[] = [];

  for (const x of xMidpoints) {
    for (const y of yMidpoints) {
      containers.push({
        style: {
          position: "absolute",
          x: x - halfTileSize,
          y: y - halfTileSize,
          height: tileSize,
          width: tileSize
        }
      });
    }
  }

  return containers;
}

I don't see anything wrong with that, and no errors display in VS Code, yet it won't compile. 😕

@TAGC
Copy link

TAGC commented Jun 20, 2018

@robertpenner I think it's because that was in a Vue file. I've found out that there's a few issues in integrating Vue with TypeScript.

Edit: scratch that, it's unlikely that was in a Vue component file, since I don't need to export functions in those. Although I was probably consuming that function within a Vue component and maybe that's what caused the issue.

@leoyli
Copy link

leoyli commented Apr 4, 2019

I still have question on this:

[].concat(a, b);  // <- not good
Array().concat(a, b) // <- okay

I have a use case that a, b are in the same type but one of them are also possible to be undefined. I want to merge them into an array without mutation... The firs expressional approach failed but the second functional approach not give me any error?!

I'm using this workaround since .concat is not happy to be undefined which is fine (it won't extend the array but just return a copy).

@s-ong-c
Copy link

s-ong-c commented Aug 6, 2019

const getAverage = (numbers: number[]) => {
    console.log('getAverage');
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a,b) => a + b);
    return sum / numbers.length;
}
interface AverageProps{}

const Average: React.SFC<AverageProps> = props => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');
    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setNumber(e.target.value);
    }
    const onInsert = (e: React.MouseEvent<HTMLButtonElement>) => {
        console.log(number);
        const nextList = list.concat(parseInt(number));
        setNumber('');

    }

why type error const nextList = list.concat(parseInt(number));

@danielmorell
Copy link

Here is a short list of every way I can think of writing this...

let foo: string[] = ['one', 'two', 'three'];

// Bad - this will throw a compile error.
let one: string[] = [].concat(...foo);

// Good
let two: string[] = Array().concat(...foo);

// Good
let three: string[] = [];
three.concat(...foo);

// Good
let four: string[] = ([] as string[]).concat(...foo); 

// Good - but throws an error in the playground because it thinks this the type annotation is JSX.
// You must set JSX to "none" to remove the error.
let four: string[] = (<string[]>[]).concat(...foo);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests