Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lambda generics are inaccessable inside lambda body #55617

Closed
awerlogus opened this issue Sep 3, 2023 · 7 comments
Closed

Lambda generics are inaccessable inside lambda body #55617

awerlogus opened this issue Sep 3, 2023 · 7 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@awerlogus
Copy link

πŸ”Ž Search Terms

access generic type inside lambda function body

πŸ•— Version & Regression Information

5.3.0-dev.20230831

⏯ Playground Link

No response

πŸ’» Code

const a = <T>(_: T): void => {
  // No error
  type A = T
}

const b: <T>(_: T) => void = _ => {
  // Cannot find name 'T'.ts(2304)
  type A = T
}

πŸ™ Actual behavior

Currently if you define a function as a lambda expression and the type of this lambda is inferred from the type of the variable it's assigned to, attempting to access generic types within the lambda's body results in an error message.

πŸ™‚ Expected behavior

TypeScript to permit the use of generic types within the body of a lambda expression, regardless of whether the lambda's type is explicitly defined or inferred from the variable it's assigned to.

Additional information about the issue

Real world example:

/** @type {<P, R>(set: ReadonlySet<P>, func: Arrow<P, R>) => Set<R>} */
const map = (set, func) => {
  // Have to write Set<ReturnType<typeof func>>
  // But it would be nice to write Set<R> instead
  /** @type {Set<ReturnType<typeof func>>} */
  const result = new Set()

  for (const elem of set) {
    result.add(func(elem))
  }

  return result
}

Quite often, extracting generic types from parameter types may be challenging since it requires deconstruction of the parameter type. Furthermore, the laziness of type inference can frequently make the result type unusable.

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 3, 2023

Your lambda b does not have a type argument T, so it's not accessible.

I believe what you want (infer the existence of the generic argument based on the context) would require #47599.

n your real world example you can resolve it by declaring the existence of a type argument:

/**
 * @template P,R
 * @type ...
 */

@fatcerberus
Copy link

What @MartinJohns said; to be clear, what you've done is assigned a non-generic function _ -> { ... } to a generic type. The type parameter T is therefore not accessible in the implementation because that's not actually generic. Contextual typing can infer the type of untyped parameters (such as _) but it won't introduce type parameters.

There is a potential bug here, though: _ is inferred by contextual typing as T which doesn't really make sense.

@fatcerberus
Copy link

It maybe makes more sense why b should be an error if you rewrite the code like this:

const f = (_: unknown) => {
    type A = T  // error, what even is T here
};
const b: <T>(_: T) => void = f;

@Andarist
Copy link
Contributor

Andarist commented Sep 4, 2023

There is a potential bug here, though: _ is inferred by contextual typing as T which doesn't really make sense.

The inner function actually is being inferred as a generic function - based on the context. See those lines:
https://github.dev/microsoft/TypeScript/blob/5d04803196899fab535103a5114bc9bf8d04dbd9/src/compiler/checker.ts#L35619-L35623

It actually makes sense. If we hover over inner here we'll see (local function) inner<T>(_: T): void:

const test1: <T>(_: T) => void = function inner(_) {};

Without it, we wouldn't be able to write this:

const identity: <T>(arg: T) => T = arg => arg

So T is and should be accessible within this lambda... it's just that it's not exactly accessible publicly. It's perhaps unfortunate but scope-wise it makes sense to me.

@fatcerberus
Copy link

So Martin was wrong and it is inferring the existence of (anonymous) type parameters? I guess that makes sense but it's not really useful if you have no way to refer to them. Hence this issue, I guess.

@Andarist
Copy link
Contributor

Andarist commented Sep 4, 2023

Isn't the identity case presented above proving its usefulness? The arg could go through a longer/more complicated function body and the return type could be much more complex than just T but thanks to that mechanism the return statements can still be strictly type-checked based on the contextual return type, despite the type parameter being sort of invisible here.

@andrewbranch andrewbranch added the Working as Intended The behavior described is the intended behavior; this is not a bug label Sep 5, 2023
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Sep 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants