zaplec
zaplec

Reputation: 1819

Why is TypeScript showing "Cannot invoke an object which is possibly 'undefined'.ts(2722)" error after optional chaining operator?

During this stayhome period I decided to dive into TypeScript and started to practice it by implementing some basic data structures. I am trying to implement a custom stack that uses custom nodes.

My StackNodes are defined like this:

class StackNode {

  private val: any;
  private nxt: StackNode | undefined = undefined;

  constructor(val: any, nxt?: StackNode | undefined) {
    this.val = val;
    this.nxt = nxt || undefined;
  }

  get value(): any {
    return this.value;
  }

  get next(): StackNode | undefined {
    return this.next;
  }

}

export default StackNode;


And the actual Stack:

class Stack {

  private capacity!: number;
  private top?: StackNode | undefined = undefined;
  private size: number = 0;

  constructor(capacity: number, initialValues?: Array<any>) {
    this.capacity = capacity;

    if (initialValues) {
      this.size = initialValues.length;
      this.top = this._initStack(initialValues, initialValues.length - 1);
    }

  };

  private _initStack = (array: Array<any>, idx: number): StackNode => {
    if (idx == 0) {
      return new StackNode(array[idx], undefined);
    } else {
      return new StackNode(array[idx], this._initStack(array, idx-1));
    }
  }

  pop(): any {
    const value = this.top?.value();
    this.top = this.top?.next();
    return value;
  }

}

export default Stack;

The problem here is the line with the optional chaining operator in pop-method this.top = this.top?.next()

What I have understood is that the expression this.top?.next() should be equivalent to

(this.top === null || this.top === undefined)? undefined : this.top.next()

but I still get the error

Cannot invoke an object which is possibly 'undefined'.ts(2722)

when the call is made even though it shouldn't be undefined anymore at that stage.

Why's that? What am I missing here? Both the StackNode.nxt and Stack.top are allowed to be undefined. I have tried to do it in the old way like this:

if (this.top !== null || this.top !== undefined) {
  const value = this.top.value()
  this.top = this.top.next()
}

But I still get the same error, even though here it should be sure that the this.top can not be undefined, but has to be, or at least should be, of type StackNode.

How this should work is that when popping from empty stack, pop method would return undefined and when popping the last element, its next, that is undefined, is set as the top of the stack.

I am using TS 3.8.3

Upvotes: 1

Views: 4468

Answers (1)

Rubydesic
Rubydesic

Reputation: 3476

You define next as a getter, so it must be accessed like so: this.top = this.top?.next

The only reason that const value = this.top?.value(); even compiles is because you use 'any' (DONT DO THAT, EVER!!), and typescript assumes that get value might return a function that you are invoking.

You should define StackNode using generics. For example,

class StackNode<T> {

  private val: T;
  private nxt: StackNode<T> | undefined = undefined;

  constructor(val: T, nxt?: StackNode<T> | undefined) {
    this.val = val;
    this.nxt = nxt || undefined;
  }

  get value(): T {
    return this.value;
  }

  get next(): StackNode<T> {
    return this.next;
  }

}


class Stack<T> {

  private capacity!: number;
  private top?: StackNode<T> | undefined = undefined;
  private size: number = 0;

  constructor(capacity: number, initialValues?: Array<any>) {
    this.capacity = capacity;

    if (initialValues) {
      this.size = initialValues.length;
      this.top = this._initStack(initialValues, initialValues.length - 1);
    }

  };

  private _initStack = (array: Array<any>, idx: number): StackNode<T> => {
    if (idx == 0) {
      return new StackNode(array[idx], undefined);
    } else {
      return new StackNode(array[idx], this._initStack(array, idx-1));
    }
  }

  pop(): T | undefined {
    const value = this.top?.value(); //doesn't compile
    this.top = this.top?.next(); //doesn't compile either
    return value;
  }

}

Then, const value = this.top?.value(); would not have compiled either.

Upvotes: 1

Related Questions