GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Attempt at implementing propagated inference for free type parameters (#9366) by kpdonn · Pull Request #24626 · microsoft/TypeScript

Conversation

@kpdonn

@kpdonn Kevin Donnelly (kpdonn) commented Jun 2, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

I started playing with this as a tangent off of #23809 after I was starting to add some type inference there and was running into a lot of {} inferences. Initially I wasn't planning on getting anywhere near something worth sharing but after I played with it for a while it started to feel like it would be possible to come up with something that could at least serve as a working straw-man implementation of #9366, which I think I ended up accomplishing.

Check out the updated baselines (especially inferringGenericFunctionsFromGenericFunctions2.ts which is mostly borrowed from Marin Marinov (@gcnew)'s extremely helpful test suite) for full details on how it behaves, but it mostly does what it should for the examples I've tried:

// everything below compiles without error

// example from https://github.com/Microsoft/TypeScript/issues/9366
function flip<a, b, c>(f: (a: a, b: b) => c): (b: b, a: a) => c {
    return (b: b, a: a) => f(a, b);
}
function zip<T, U>(x: T, y: U): [T, U] {
    return [x, y];
}

const flipped = flip(zip);
var expected: <T, U>(y: U, x: T) => [T, U] = flipped;
const actualCallResult = flipped("test", 1234)
const expectedResult: [number, string] = actualCallResult;


// from https://github.com/Microsoft/TypeScript/issues/16414
declare function compose<A, B, C>(f: (x: A) => B, g: (y: B) => C): (x: A) => C;
declare function box<T>(x: T): { value: T };
declare function list<U>(x: U): U[];

const composed = compose(list, box);
const expectedComposed: <U>(u: U) => { value: U[] } = composed;
const callComposed = composed("test");
const expectedCallComposed: { value: string[] } = callComposed;

const composedContextual = compose(x => [x], x2 => ({ boxed: x2 }));
const expectedComposedContextual: <U>(u: U) => {boxed: U[]} = composedContextual;
const callComposedContextual = composedContextual("test");
const expectedCallComposedContextual : {boxed: string[]} = callComposedContextual;

It also manages to only make a few changes to existing baselines, and the changes it does have seemed positive or neutral to me.

For a high level summary of the implementation that glosses over a lot of the details:

  • The first thing I did was change getInferredType() to return the type parameter being inferred if there are no inferences available for it instead of defaulting to {}. The inference does not become fixed if this happens.
  • In resolveCall(), immediately after chooseOverload() successfully returns a signature I resolve the return type of the chosen signature and check to see if it contains any free type parameters. (This is done eagerly now. A polished version should probably be refactored to be lazy like everything else)
  • If the return type does contain free type parameters then I see whether the the return type is a type with a single call signature and nothing else. If it is then I clone the signature and the free type parameters and add the free type parameters as explicit type parameters of the new signature. A new return type is created from the new signature and replaces the old one.
  • If the return type contained free type parameters but is anything besides a type with a single call signature and nothing else, then the free type parameters are erased to {} like they would have been before this PR.
  • All of the above logic about free type parameters is ignored for type parameters that are found in the outerTypeParameters of the call expression node. This avoids erasing type parameters for calls inside the body of a function with generics.
  • Also, if strictFunctionTypes is disabled then the old behavior of defaulting to {} when there are no inferences still happens. This is because I ran into assignability problems if contravariant inferences of type parameters were treated as covariant inferences, which is what happens without strictFunctionTypes.
  • inferFromSignatures() will try to instantiate a source signature with type parameters in the context of the target signature in the same way as compareSignaturesRelated(). This also only happens if strictFunctionTypes is enabled.
  • When chooseOverload() has multiple candidate signatures to choose from and has some contextually typed arguments, any contextual types that were assigned are reset when trying the next candidate signature.

Plan from here

I don't have any specific plans for this from here, I just wanted to share what I have since it turned out pretty well. If anyone tries it out and runs into things that aren't working right let me know and I'll see if I can handle them. Otherwise though I'm probably going to let this stand as it is now unless the TS team expresses interest in this approach, which they very easily may not since this obviously makes significant changes to the core inference and signature resolution code.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

pelotom, minedeljkovic, Birowsky, falsandtru, ivogabe, piotrwitek, gcnew, KiaraGrouwstra, ikatyang, bitjson, and 55 more reacted with thumbs up emoji nateabele, clayne11, Birowsky, sledorze, KiaraGrouwstra, ikatyang, bitjson, xdrop, evg656e, alvis, and 20 more reacted with hooray emoji sledorze, KiaraGrouwstra, nateabele, ikatyang, ksaldana1, alinhle, Joshuaweiss, Ffisegydd, ferhtgoldaraz, quicksnap, and 12 more reacted with heart emoji
All reactions
  • 👍 65 reactions
  • 🎉 30 reactions
  • ❤️ 22 reactions
Kevin Donnelly (kpdonn) added 3 commits June 2, 2018 12:06
@kpdonn
Add some tests for existing behavior inferring from generic functions.
f4a07ae
@kpdonn
Changes to allow propagating type parameters with no inferences.
664c4d9
A summary of the bigger pieces I can think of:

* When strictFunctionTypes is true if there are no inferences for a
type parameter then the type parameter itself will be returned instead
of defaulting to `{}`. After chooseOverload returns a successful
signature, the return type of that signature is checked and if the
return type is a function type with a single call signature then any
free type parameters found in that call signature are converted to type
parameters for the call signature. If the return type is not a call
signature then any free type parameters are converted to `{}` at that
point.

* inferFromSignatures will instantiate the source in the context of the
target if the source signature has type parameters and
strictFunctionTypes is true.

* StrictFunctionTypes is required in both of the above cases because of
errors that pop up otherwise when there are not contravariant inferences.

* Seemingly free type parameters that are outerTypeParameters of the
call being inferred are ignored instead of being erased. This is to
avoid erasing type parameters when calling functions inside the body of
a generic function.

* When choosing an overload with multiple signatures that have
contextually typed arguments, any contextual types that are assigned are
reset when trying a new candidate.

Updated baselines will be committed in the following commit.
@kpdonn
Update baselines.
2e51a07
@kpdonn Kevin Donnelly (kpdonn) mentioned this pull request Jun 2, 2018
Supporting generic type inference over the other higher-order functions #9366
Closed
@kpdonn
Forgot that I had disabled building with local compiler.
ccb5dd4
@kpdonn
Kevin Donnelly (kpdonn) force-pushed the propagateFreeTypeParameters branch from 20264f0 to ccb5dd4 Compare June 2, 2018 20:33
@clayne11

Curtis Layne (clayne11) commented Jun 2, 2018

Copy link
Copy Markdown

This is such an important missing feature in TS, I really hope this gets picked up.

nateabele, AaronFriel, KiaraGrouwstra, alinhle, Joshuaweiss, Ffisegydd, joshburgess, sledorze, kujon, CharlBest, and 7 more reacted with thumbs up emoji sledorze, KiaraGrouwstra, alinhle, joshburgess, and CharlBest reacted with laugh emoji
All reactions
  • 👍 17 reactions
  • 😄 5 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@falsandtru

falsandtru (falsandtru) commented Jun 3, 2018

Copy link
Copy Markdown
Contributor

Nathan Shively-Sanders (@sandersn) Anders Hejlsberg (@ahejlsberg) Can you take a look?

nateabele, KiaraGrouwstra, clayne11, alinhle, Joshuaweiss, joshburgess, sledorze, and CharlBest reacted with thumbs up emoji sledorze and joshburgess reacted with laugh emoji
All reactions
  • 👍 8 reactions
  • 😄 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@KiaraGrouwstra

kiara (KiaraGrouwstra) commented Jun 4, 2018

Copy link
Copy Markdown
Contributor

best addition since conditional types! Ramda badly needs this. /cc Ika (@ikatyang)

ikatyang, bitjson, clayne11, alinhle, Joshuaweiss, Ffisegydd, sarod, joshburgess, nateabele, hastebrot, and 12 more reacted with thumbs up emoji
All reactions
  • 👍 22 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joshburgess Josh Burgess (joshburgess) mentioned this pull request Jul 25, 2018
Function composition challenge for type system #10247
Open
@joshburgess

Josh Burgess (joshburgess) commented Jul 25, 2018

Copy link
Copy Markdown

Any updates from the TS team? TypeScript needs this feature.

nateabele, gcanti, niba, sledorze, kujon, clayne11, derrickbeining, kristian-puccio, and nhardy reacted with thumbs up emoji
All reactions
  • 👍 9 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Jul 27, 2018

Copy link
Copy Markdown
Member

Kevin Donnelly (@kpdonn) Thanks for taking time to experiment with this. It definitely would be nice to be able to infer free type variables--but it is quite complicated to do right, as I'm sure you've discovered. Your particular approach works for some scenarios, but there are several common ones that really only can be solved using a unification algorithm. For example:

declare function compose<A, B, C>(f: (x: A) => B, g: (y: B) => C): (x: A) => C;
declare function arrayToList<T>(x: Array<T>): List<T>;
declare function box<U>(x: U): { value: U };

const f = compose(arrayToList, box);  // Should infer <T>(x: Array<T>) => { value: List<T> }

Ideally this would infer type <T>(x: Array<T>) => { value: List<T> } for f, but it requires a more capable unification algorithm to do so, along the lines of what Marin Marinov (@gcnew) prototyped. A number of the challenges are outlined in #15016 (comment)). It would definitely be complex to integrate unification in a non-breaking manner.

But I totally understand the value of the feature and it is certainly something I continue to think about.

xdrop, falsandtru, sledorze, piotrwitek, evg656e, kujon, joshburgess, jethrolarson, karol-majewski, CharlBest, and 12 more reacted with thumbs up emoji
All reactions
  • 👍 22 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kujon

Jakub Korzeniowski (kujon) commented Jul 27, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

I'd like to add that your typical FP library is not the only place where the lack of this feature is making things hard. Writing react components with generics, even though it's supported, is pretty much impossible because of this (as soon as you drop your component into a typical HOC, you'll get {} everywhere).

nateabele, joshburgess, KiaraGrouwstra, damncabbage, barbalex, and itajaja reacted with thumbs up emoji
All reactions
  • 👍 6 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@clayne11

Curtis Layne (clayne11) commented Jul 27, 2018

Copy link
Copy Markdown

For React, we've moved away from using HOCs and moved to using render props instead since it plays a lot better with TypeScript at the moment.

karol-majewski reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) added the Experiment A fork with an experimental idea which might not make it into master label Sep 6, 2018
@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) added this to the Community milestone Sep 17, 2018
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Oct 17, 2018
Inferred type does not work for generic reduce function with when not used as a lambda #27708
Closed
@EvanCarroll

Evan Carroll (EvanCarroll) commented Jan 17, 2019

Copy link
Copy Markdown

Is this patch dead? Perhaps TS could create some tests they want to see pass, and we can either get the job done or bounty it off?

nateabele, sledorze, arutkowski00, MattiasMartens, clayne11, tregusti, and anurbol reacted with thumbs up emoji matthargett reacted with heart emoji
All reactions
  • 👍 7 reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nateabele

Nate Abele (nateabele) commented Jan 31, 2019

Copy link
Copy Markdown

I realize the degree of complexity involved here, but is there any way we could get an update from a TS core gatekeeper?

Despite the (already considerably high) level of sophistication & expressiveness of the type system, it is still effectively impossible to do real, type-safe FP beyond naïve function composition. A proper implementation of this feature would be a massive step forward.

If we could at least know what the goalposts are for getting this accepted, I'm sure more than a few of us (myself included) would be happy to step up and help, but it's hard to want to put up the effort when it feels like you might just as likely be shouting into the void, so to speak.

Anyway, I appreciate that this is a large open source project with many competing priorities, I appreciate the efforts made on it, and your time and attention. Thanks.

OliverJAsh, elyukai, EvanCarroll, KiaraGrouwstra, clayne11, tregusti, jadbox, jimmed, bySabi, nythrox, and mwstr reacted with thumbs up emoji
All reactions
  • 👍 11 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nateabele Nate Abele (nateabele) mentioned this pull request Jan 31, 2019
TypeScript Roadmap: January - June 2019 #29288
Closed
@RyanCavanaugh

Ryan Cavanaugh (RyanCavanaugh) commented Feb 1, 2019

Copy link
Copy Markdown
Member

Nate Abele (@nateabele)

If we could at least know what the goalposts are for getting this accepted, I'm sure more than a few of us (myself included) would be happy to step up and help, but it's hard to want to put up the effort when it feels like you might just as likely be shouting into the void, so to speak.

Three things here.

First, we do have far too many open PRs at the moment, and we have "dropped the ball" on following up on community members. I apologize for that, and we're taking steps to address this: all open PRs have been assigned to a core team member for investigation and follow-up. For transparency, this backlog started building while I was on paternity leave coinciding with two engineers taking other opportunities, and we haven't backfilled those positions yet, so we're quite short-staffed -- but we need to reprioritize things here to keep things moving. My goal here is that we don't any actionable community PRs sitting open for more than a few days.

Second, a key aspect of this is "actionable" - we're basically staffed at a level where we can readily accept PRs for bugs and features which are already "approved", meaning that we understand the impact on the language and are comfortable accepting a correct PR implementing that change. We're also barely staffed to work through the current backlog of 1,200 open "Suggestions" before the heat death of the universe. A PR for a language feature with no approved issue tracking it is just not something we're equipped for in terms of process: The design needs to be hammered out and approved before the PR can be looked at, otherwise there are just too many free variables in play. Again for transparency, while there's substantial overlap between design and implementation on the team, in general there are different people and processes addressing design issues vs implementation issues.

This is basically to say that this PR is "outside our process", hence the "Experiment" tag and subsequent lack of responses. Without an issue describing the design being implemented, the only thing we (as code reviewers) can comment on is whether the code is facially correct (not useful), not whether or not we cant merge it. IOW, we (as designers) can't take PRs to the design process because it's not useful to just put the code up on screen and reverse engineer what's happening. A useful next step to make this more actionable would be a fresh issue outlining what proposal is being implemented and how.

Third, the comments above from Anders Hejlsberg (@ahejlsberg) are effectively blocking to the design being implemented. Speaking with him offline, we're in agreement that only a full (or "nearly full") unification algorithm will produce correct inferences in all the cases of interest here. Going back to an issue outlining the proposed design would be a clearer venue for going over various examples and how a potential unification algorithm would handle them.

Kinrany, Bnaya, tp, karlhorky, Jessidhia, jimmed, SlurpTheo, and alexrock reacted with thumbs up emoji matthargett reacted with heart emoji
All reactions
  • 👍 8 reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@MattiasMartens MattiasMartens mentioned this pull request Feb 5, 2019
Declare a function as a type; or, defer resolving generic types when declaring a variable as a type #29613
Closed
5 tasks done
@johnrom John Rom (johnrom) mentioned this pull request Jan 20, 2020
Use React.forwardRef in <Formik> jaredpalmer/formik#2208
Open
@sandersn

Nathan Shively-Sanders (sandersn) commented Feb 3, 2020

Copy link
Copy Markdown
Member

Kevin Donnelly (@kpdonn), thanks for this prototype. I'm cleaning up our PRs and since the original bug was closed with #30215, I'm going to close this.

We continue to think about how to make inference better, so we would appreciate any more discussion on the topic. Prototypes are good too!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sandersn Nathan Shively-Sanders (sandersn) closed this Feb 3, 2020
@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Experiment A fork with an experimental idea which might not make it into master

Projects

None yet

Milestone

Community (deprecated)

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

11 participants

@kpdonn @clayne11 @falsandtru @KiaraGrouwstra @joshburgess @ahejlsberg @kujon @EvanCarroll @nateabele @RyanCavanaugh @sandersn
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.