Skip to content

Set.has() should accept any value, and could be a type predicate #60547

@jthemphill

Description

@jthemphill

⚙ Compilation target

ES2015

⚙ Library

lib.es2015.collection.d.ts

Missing / Incorrect Definition

/**
* @returns a boolean indicating whether an element with the specified value exists in the Set or not.
*/
has(value: T): boolean;

I believe

Set<T>.has(value: T): boolean

should become

Set<T>.has(value: unknown): value is T

This applies for other methods as well, such as Map<K, V>.has(key: K): boolean, as well as other ES standards.

Essentially, it's not an error to pass an arbitrary value to Set.has(). It doesn't lead to a crash, exception, or error of any sort, even when we pass weird values like {}, [], or NaN, so the typechecker shouldn't stop us from doing it. We simply get a true if the value is in the Set, or a false if the value is not. And since the Set can only contain values of type T, the boolean returned by Set.has() also becomes proof that value is of type T. This makes it an ideal candidate for a type predicate.

This would immediately fix several regressions in our codebase that we saw when we upgraded TypeScript from 5.3 to 5.5. TypeScript's inferred type predicates are amazing, but they narrowed the types of our Sets and caused several of our Set.has() method calls to become errors. The sample code is a simplified example of what we've found in the wild in our codebase.

Sample Code

declare function defaultAttrs(subtype: string): string[]
declare function getAttrs(): string[]

type MyAttribute = `_${string}`
function isMyAttribute(key: string): key is MyAttribute {
  return key.startsWith('_')
}

export function myAttrs(subtype: string) {
  const defaults = defaultAttrs(subtype)
  return new Set(
    Object.keys(defaults).filter((key) => {
      return isMyAttribute(key)
    }),
  )
}

function attrInSet(subtype: string) {
  const myAttrSet = myAttrs(subtype)
  for (const attr of getAttrs()) {
    // Argument of type 'string' is not assignable to parameter of type '`_${string}`'.ts(2345)
    if (myAttrSet.has(attr)) {
      return true
    }
  }
  return false
}

Documentation Link

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions