44458

Disallow call with any

Question:

Consider following function overloads:

function f(key: undefined); function f(key: string | undefined, value: object | undefined);

I want to make eligible calls with single explicit undefined f(undefined), but require two arguments for all other cases. And owerloads above work fine, until I pass a variable with type any - seems like any can be casted to undefined (yes, it seems logical as it is any).

<strong>How can I disallow call with single any argumnent?</strong>

<hr />

Full <a href="https://www.typescriptlang.org/play/#src=function%20f(key%3A%20undefined)%3B%0D%0Afunction%20f(key%3A%20string%20%7C%20undefined%2C%20value%3A%20object%20%7C%20undefined)%3B%0D%0A%0D%0Afunction%20f(key%3A%20string%20%7C%20undefined%2C%20value%3F%3A%20object%20%7C%20undefined)%20%7B%0D%0A%20%20%20%20console.log(key%2C%20value)%3B%0D%0A%7D%0D%0A%0D%0A%2F%2F%20No%20errors%20-%20RIGHT%0D%0Af(undefined)%3B%0D%0Af(%22%22%2C%20%7B%7D)%3B%0D%0Af(%22%22%2C%20undefined)%3B%0D%0Af(undefined%2C%20undefined)%3B%0D%0Af(undefined%2C%20%7B%7D)%3B%0D%0A%0D%0A%2F%2F%20Errors%20-%20RIGHT%0D%0Af(%22%22)%3B%0D%0A%0D%0A%2F%2F%20No%20errors%20-%20WRONG%0D%0Adeclare%20var%20x%3A%20any%3B%0D%0Af(x)%3B" rel="nofollow">demo code</a>:

function f(key: undefined); function f(key: string | undefined, value: object | undefined); function f(key: string | undefined, value?: object | undefined) { console.log(key, value); } // No errors - RIGHT f(undefined); f("", {}); f("", undefined); f(undefined, undefined); f(undefined, {}); // Errors - RIGHT f(""); // No errors - WRONG declare var x: any; f(x);

Answer1:

TypeScript really doesn't want to disallow any from matching a type, since that's the whole point of any. You might want to rethink any code which relies on rejecting any, so tread lightly.

<hr />

That being said, you can use the new <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#conditional-types" rel="nofollow">conditional types</a> feature to build a detector for any which can then be used to disallow an any variable.

Here's the detector:

type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;

The type constraint 0 extends 1 is not satisfied (0 is not assignable to 1), so it should be impossible for 0 extends (1 & T) to be satisfied either, since (1 & T) should be even narrower than 1. However, when T is any, it reduces 0 extends (1 & any) to 0 extends any, which is satisfied. That's because any is intentionally <a href="https://basarat.gitbooks.io/typescript/docs/types/type-compatibility.html#soundness" rel="nofollow">unsound</a> and acts as both a supertype and subtype of <em>almost</em> every other type. Therefore, IfAny<T, Y, N> checks if T is any. If so, it returns Y. If not, it returns T. Let's see it work:

type IsAny<T> = IfAny<T, true, false> const yes: IsAny<any> = true; const no: IsAny<string> = false; <hr />

Recall that I said any matches <em>almost</em> every other type. The only type that <em>doesn't</em> match any is never:

declare const any: any; const never: never = any; // error, any is not assignable to never

We need that fact too, in order to reject any parameters. Let's change the first signature of f() from

function f(key: undefined): void;

to

function f<K extends IfAny<K, never, undefined>>(key: K): void;

We've made the key a generic type K that is constrained to IfAny<K, never, undefined>. If K is not any, then that constraint is just undefined, so K can only be undefined as desired. If K <em>is</em> any, then that constraint becomes never, and since any does not match never, it will fail to meet the constraint.

When we use the above signature, you see the following behavior:

f(undefined); // still works f(""); // still error, "" is not assignable to undefined declare var x: any; f(x); // now error, any is not assignable to never

which is what you wanted.

Hope that helps; good luck!

Recommend

  • Using Checkboxes to contol an Input.value (With an annoying twist.)
  • Reference.push failed: first argument contains undefined in property 'confirm.orderTotal'
  • React JS 'this' not working as expected
  • Git Configuration with eclipse--not able to push code from eclipse to remote git server
  • regex question for removal of javascript malware
  • Observable.forkJoin() TS2322 error after updating to TypeScript 2.4.1 and Rxjs 5.4.2
  • Graph matplotlib to show total count in the histogram bins
  • Best Practice for Cell Editing (Single-Click or Double-Click) in Table in Swing
  • Raise Session_OnStart event from custom ASP.NET SessionStateProvider class
  • Using Delphi + Jedi, losing USB data when device sends it “too fast”
  • Add Jpanel to Jframe NetBeans
  • Incorrect behaviour when selecting chips in Angular Material
  • How to convert Neo4j Result to GraphJSON
  • Recursion in ASP.NET Core Razor views
  • What does the TypeScript “lib” option really do?
  • Regex for Specific Tag
  • Angular Bootstrap Carousel Slide Transition not working correctly
  • How to revert to previous XCode version?
  • Not able to aggregate on nested fields in elasticsearch
  • Swift: Switch statement fallthrough behavior
  • Make VS2015 use angular-cli ng at build time in a .NET project
  • Android screen density dpi vs ppi
  • How would I use PHP exceptions to define a redirect?
  • How to extract text from Word files using C#?
  • Where to put my custom functions in Wordpress?
  • Can a Chrome extension content script make an jQuery AJAX request for an html file that is itself a
  • Upload files with Ajax and Jquery
  • How to pass list parameters for each object using Spring MVC?
  • Numpy divide by zero. Why?
  • How to delete a row from a dynamic generate table using jquery?
  • Proper way to use connect-multiparty with express.js?
  • Acquiring multiple attributes from .xml file in c#
  • using HTMLImports.whenReady not working in chrome
  • What are the advantages and disadvantages of reading an entire file into a single String as opposed
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)
  • need help with bizarre java.net.HttpURLConnection behavior
  • Authorize attributes not working in MVC 4
  • How can I remove ASP.NET Designer.cs files?
  • EntityFramework adding new object to nested object collection
  • java string with new operator and a literal