lentschi
lentschi

Reputation: 310

Typescript type preventing comparison with other primitive types

Prerequisites: I have a variable, that I always just pass through and never manipulate or compare inside my app. (It comes from the server in one API property and is returned to the server unaltered in a different API property.)

My question: Is there a typescript type that prevents that this variable may ever be accidentally compared to another variable of a different type?

Example:

type MyUnknownPassthroughServerType = string;

function getSomethingFromTheServer(): MyUnknownPassthroughServerType {
  return <MyUnknownPassthroughServerType>'test';
}

function writeSomethingToTheServer(payload: MyUnknownPassthroughServerType): void {}

const serverVar = getSomethingFromTheServer();

if (serverVar === 'test') { // <- I want a compilation error on this
  // Do something
}

if (serverVar === 1) { // <- I want a compilation error on this
  // Do something
}

writeSomethingToTheServer(serverVar); // <- That should be fine
writeSomethingToTheServer('test') // <- I want a compilation error on this

Ugly solution: Declaring it as some type that will never have any overlap with another type would work of course (e.g. type MyUnknownPassthroughServerType = {neverUsed: boolean};). But isn't there a better way to do this?

If that is not possible in typescript alone, I could also resort to an eslint solution, if you know of any...?

Upvotes: 2

Views: 201

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074355

I don't think it's possible to define a type that will make all comparisons with it (except itself) fail. Your "ugly" solution achieves most of what you want to do and is probably your best bet, though you might use unique symbol rather than boolean (because I can create a {neverused: true} object). Sadly, though, comparisons with object are allowed, but it at least disallows comparisons with specific types of objects:

type MyUnknownPassthroughServerType = { readonly __never_used__: unique symbol };

function getSomethingFromTheServer(): MyUnknownPassthroughServerType {
    return "test" as any as MyUnknownPassthroughServerType;
}

function writeSomethingToTheServer(payload: MyUnknownPassthroughServerType): void {}

const serverVar = getSomethingFromTheServer();

writeSomethingToTheServer(serverVar); // <- Works as desired
writeSomethingToTheServer("test"); // <- Compilation error as desired

// Strings are not allowed
serverVar === "test"; // <- Compilation error as desired

// Numbers are not allowed
serverVar === 1; // <- Compilation error as desired

// Booleans are not allowed
serverVar === true; // <- Compilation error as desired

// `object` is allowed :-(
declare const someObject: object;
serverVar === someObject; // <- Sadly, no error here

// But not more specific objects
declare const someObject2: { a: number };
serverVar === someObject2; // <- Compilation error as desired
declare const someRecord: Record<string, number>;
serverVar === someRecord; // <- Compilation error as desired

// `null` and undefined are allowed
serverVar === null;
serverVar === undefined;

Playground link

Another trick is string & { _tag: 'MyUnknownPassthroughServerType' } as captain yossarian from Ukraine pointed out, but it allows comparisons with strings.

Upvotes: 2

Related Questions