9

I have a class defined like this

class Foo {
   value: string | null;
   constructor(){
      this.value = null;
   }
   private ensureValueExists():this.value is string{ //type predicate is not legal
      this.value = "bar";
      return true;
   }
   doStuffWithValue(){
      this.ensureValueExists();
      return 5 + this.value;  //ERROR, this.value can be null
   }
} 

I would like for the ensureValueExists method to be able to tell to the compiler that this.value is indeed a string and safe to use. Is there a special syntax to use or is it no doable with TS at the moment for methods ?

5
  • this.value ??= "bar"; Commented Jul 7, 2021 at 16:25
  • 2
    I would prefer to get my type predicate to work, so i don't have to add guards everywhere. Commented Jul 7, 2021 at 16:28
  • If you can ensure that value will be initialised later but still would be available before "real code" (non-initialisation code) access it, then you can note it as value!: string. If you want null-safe access you can say this.value ?? "fallback". If you want to make sure you produce an object where value is treated as non-null you can have a separate interface. It's a bit hard to say which is the correct choice here. Commented Jul 7, 2021 at 16:28
  • "I would prefer to get my type predicate to work, so i don't have to add guards everywhere." But you'll still have to add assertions everywhere Commented Jul 8, 2021 at 6:36
  • I don't understand, with the solution from jcalz it works without a non-null assertion. Commented Jul 8, 2021 at 10:16

2 Answers 2

12

You can use an assertion method which narrows this. Support for that isn't particularly clear from the documentation, although a commit associated with microsoft/TypeScript#32695, the PR implementing assertion functions, demonstrates that it's possible.

So in your case it would look like:

  private ensureValueExists(): asserts this is { value: string } {
    this.value = "bar";
  }

(Note that you can't return anything in an assertion function/method), and then the following works:

  doStuffWithValue() {
    this.ensureValueExists();
    return 5 + this.value;  // okay
  }
}

There are definitely caveats associated with assertion functions and methods, but since you're only operating on this, you aren't running into them here.

Playground link to code

Sign up to request clarification or add additional context in comments.

5 Comments

I don't understand how this is even possible. Nice hack.
I am getting Type 'boolean' is not assignable to type 'void'.ts(2322) in my editor when trying this but if I remove the asserts keyword the type predicate works.
> (Note that you can't return anything in an assertion function/method)
An assertion function should throw or return void. Hence, if you need a return value, the classic predicate would be better e.g. private isValueString(): this is {value: string} { return typeof this.value === 'string' ? true : false }
If one does want/need to return something from the function, just leave out the asserts part of the predicate. eg. isValid(): this is { value: string } { return this.value != null; }
0

This syntax is called a user-defined type guard. It can be used to narrow down the type of its parameter, but nothing outside of that. For example, even this code won't work:

let value: unknown

function isValueString(): value is string { // Cannot find parameter 'value'.
    return typeof value === 'string'
}

Playground link

There is also the assertion function syntax, which you probably meant to use in the first place. However, that has the same limitation as above, you can't use it on anything else than function parameters:

let value: unknown

function ensureValueIsString(): asserts value is string { // Cannot find parameter 'value'.
    value ??= 'bar'
}

Playground link

3 Comments

Yes i know, what i was asking is if it is doable with methods.
I don't think so. How about setting a default value in the constructor and making value non-nullable?
It is doable with methods, if you narrow this (which is an implicit parameter); see my answer above or below or wherever it is

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.