#Alternative to is as predicate

36 messages · Page 1 of 1 (latest)

acoustic falcon
#

If I want to check if a given function parameter fits one type, using a type guard function is fine.

What if the param fits more than one type like in this simplified example:

type MyType = {
  timestamp: number;
  value: number;
}

function isMyType(param):param is MyType(){
  return (
    param?.timestamp !== undefined &&
    param?.value !== undefined &&
    typeof param.timestamp === 'number' &&
    typeof param.value === 'number'
  )
}

const param:any = {
  timestamp:0, 
  value:0, 
  last:{
    timestamp:1,
    value:2
  }
};

if(isMyType(param) && isMyType(param.last))

Here, my IDE will say the second isMyType as a never because it assumes param only fits MyType . Is there a way to replace the is in the predicate to mean it fits the type but doesnt mean its exclusively this type? I know I could make it is MyType & {[key: string]: any;]} but I'm not sure I like this eighter.

glass spire
#

@acoustic falcon Well, in this case if you drop the any annotation it works:

vivid snowBOT
#
retsam19#0

Preview:```ts
type MyType = {
timestamp: number
value: number
}

function isMyType(param: any): param is MyType {
return (
param?.timestamp !== undefined &&
param?.value !== undefined &&
typeof param.timestamp === "number" &&
typeof param.value === "number"
...```

glass spire
#

Though a custom type-guard doesn't actually do much if you have a hard-coded object already - so I'm not sure what your actual code looks like.

#

Probably more realistically, you can add a "last" in param check before access param.last

acoustic falcon
#

actual code looks like this:

type StampedPosition = {
    timestamp: number;
    position: {
        x: number;
        y: number;
    };
};

function isStampedPosition(
    data: any,
): data is StampedPosition & {
    [key: string]: any;
} {
    return (
        data?.timestamp !== undefined &&
        data?.position.x !== undefined &&
        data?.position.y !== undefined &&
        typeof data.timestamp === 'number' &&
        typeof data.position.x === 'number' &&
        typeof data.position.y === 'number'
    );
}
class Dragable {
        ...
    private mouseData: {
        mouseMove?: StampedPosition & {
            last?: StampedPosition;
        };
        mouseDown?: StampedPosition;
    };
...
}
#

and the mouseData gets changed dynamically in different event handlers to eighter be the full mouseData or a variation allowed in the class definition

woeful glade
#

In the first example instead of saying param is any, you could use satisfies

acoustic falcon
#

but once I check the mouseData.mouseMove, I can't check the mouseData.mouseMove.last

acoustic falcon
#

but it's not allowed

woeful glade
#

Is this not allowed?

const param = {
  // ...
} satisfies MyType
acoustic falcon
glass spire
#

FWIW, a type-guard function doesn't prevent accessing other properties.

#

A type-guard's result is combined with the existing type, it doesn't completely override it.

vivid snowBOT
#
retsam19#0

Preview:```ts
declare function hasAProp(
obj: unknown
): obj is {a: string}

declare const obj: {b: string}

if (hasAProp(obj)) {
console.log(obj.a) // okay
console.log(obj.b) // okay
}```

acoustic falcon
#

why do I get Property 'last' does not exist on type 'StampedPosition'. then?

#

if (
!isStampedPosition(mouseMoveData) ||
!isStampedPosition(mouseMoveData.last)
)

glass spire
#

What type is mouseMoveData before you narrow it?

acoustic falcon
#

it's any before this if statement

glass spire
#

Yeah, I guess specifically any & SpecificType 'reduces' to SpecificType.

#

... but I don't know if I'd call that an issue with your type-guard.

#

If the question boils down to "how can I type-guard a type, while still treating it as any for other properties", you kinda can't?

acoustic falcon
#

So all I can do is eighter split mouseMoveData.last into its own variable beforehand and treat separately or have

is MyType & {
[key: string]: any;
}

Bummer that there isn't a satisfies equivalent

glass spire
#

Or add a check to convince TS that the property exists.

#
declare function hasAProp(obj: unknown): obj is { a: string };

declare const obj: unknown;

if(hasAProp(obj)) {
  console.log(obj.a); // okay
  if("b" in obj && hasAProp(obj.b)) {
    console.log(obj.b.a); // okay
  }
}
vivid snowBOT
#
samuelcharpentier#0

Preview:```ts
declare function hasAProp(
obj: unknown
): obj is {a: string}

declare const obj: unknown

function main() {
if (hasAProp(obj)) {
console.log(obj.a) // okay
if (
!hasAProp(obj) ||
("b" in obj && !hasAProp(obj.b))
)
return
console.log(obj.a)
console.log(obj.b
...```

acoustic falcon
#

Property 'b' does not exist on type '{ a: string; }'.
Property 'b' does not exist on type '{ a: string; }'.

#

this is closer to what my situation is

#

Sorry, rather this

vivid snowBOT
#
samuelcharpentier#0

Preview:```ts
declare function hasAProp(
obj: unknown
): obj is {a: string}

declare const obj: unknown

function main() {
if (
!hasAProp(obj) ||
("b" in obj && !hasAProp(obj.b))
)
return
console.log(obj.a)
console.log(obj.b)
console.log(obj.b.a)
}```

glass spire
#

I think your conditions aren't right there.

#
if(!(hasAProp(obj) && "b" in obj && hasAProp(obj.b))) return

if(!hasAProp(obj) || !("b" in obj && hasAProp(obj.b))) return

these versions both work correctly

#

(Your version is only returning if b is in the object, whereas you actually want to early-return if it isn't)

acoustic falcon
#

thank you, this should work!