Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Type guards not working as expected when using nested readonly property

Tags:

typescript

Quality can be good or bad, depending upon the type. Here type guards are working fine

enum GoodBad {
    Good = 'Good',
    Bad = 'Bad'
}
interface IQuality {
    readonly type: GoodBad;
}
interface GoodQuality extends IQuality {
    readonly type: GoodBad.Good;
}
interface BadQuality extends IQuality {
    readonly type: GoodBad.Bad;
}
type Quality = GoodQuality | BadQuality;

let quality: Quality;
if (quality.type == GoodBad.Good) {
    let goodQuality: GoodQuality = quality; // No Problem. Working good.
    let badQuality: BadQuality = quality; // Throw error. Working good.
}

But now when I wrap quality in Product then

interface IProduct {
    readonly quality: Quality;
}

interface GoodProduct extends IProduct {
    readonly quality: GoodQuality;
}
interface BadProduct extends IProduct {
    readonly quality: BadQuality;
}

type Product = GoodProduct | BadProduct;
let product: Product;
if (product.quality.type == GoodBad.Good) {
    let goodProduct: GoodProduct = product; // Throw error. Working Bad.
    // let badProduct: BadProduct = product; // Throw error. Working fine.
}

Type guard don't work as intended.

  1. Whats the difference b/w this if block and previous if block?
  2. let goodProduct: GoodProduct = product; why it is throwing error?
  3. One solution is to create another readonly type: GoodBad on IProduct. But this is extra, can this be eliminated?
like image 453
amit77309 Avatar asked Feb 03 '26 07:02

amit77309


1 Answers

The type guard will affect the quality field, not the product variable. Type-guards only impact the field that owns the discriminating field; it does not affect the owner.

So this works:

type Product = GoodProduct | BadProduct;
let product!: Product;
if (product.quality.type == GoodBad.Good) {
    let goodQuality: GoodQuality = product.quality; // Ok
    let badQuality: BadQuality = product.quality; // Err
    let goodProduct: GoodProduct = product; // Err, product not affected
    let badProduct: BadProduct = product; // Err, product not affected
}

Your solution of adding an extra field is a good one, another would be to create a custom type guard:

function isGoodProduct(p: Product)  : p is GoodProduct {
    return p.quality.type ===  GoodBad.Good
}
if (isGoodProduct(product)) {
    let goodProduct: GoodProduct = product; // OK
    let badProduct: BadProduct = product; // Err
}
like image 166
Titian Cernicova-Dragomir Avatar answered Feb 12 '26 21:02

Titian Cernicova-Dragomir