Skip to content

Return type inference breaks in function parameter; tooltip also inconsistent #32540

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

Closed
AnyhowStep opened this issue Jul 24, 2019 · 18 comments
Closed
Assignees
Labels
Needs More Info The issue still hasn't been fully clarified

Comments

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Jul 24, 2019

TypeScript Version: 3.5.1

Search Terms:

conditional type, type arg, inference, return type, argument, parameter, generic, callback function

Code

This is literally the minimum repro I could make and it's still too big.

export interface CompileError<_ErrorMessageT extends any[]> {
    /**
     * There should never be a value of this type
     */
    readonly __compileError : never;
}
/**
 * Each `string` element represents a column name.
 *
 * A "key" is a set of columns that uniquely identifies
 * a row in a table.
 */
export type Key = readonly string[];

export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
    A extends Key ?
    (
        B extends Key ?
        (
            A[number] extends B[number] ?
            A :
            never
        ) :
        never
    ) :
    never
);

export type ExtractSuperKey<
    A extends Key,
    B extends Key
> = (
    A extends Key ?
    (
        B extends Key ?
        (
            B[number] extends A[number] ?
            A :
            never
        ) :
        never
    ) :
    never
);

export type FindSubKey<
    ArrT extends readonly Key[],
    KeyT extends Key
> = (
    ExtractSubKey<
        ArrT[number],
        KeyT
    >
);
export type FindSuperKey<
    ArrT extends readonly Key[],
    KeyT extends Key
> = (
    ExtractSuperKey<
        ArrT[number],
        KeyT
    >
);

declare function noSubKey<KeyT extends Key> (
  arg : (
    (c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT &
    (
        FindSuperKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        unknown :
        CompileError<[
            KeyT,
            "is a sub key of",
            FindSuperKey<
                (("x"|"y")[])[],
                KeyT
            >
        ]>
    )
  )
) : void

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSubKey<"z"[]>
noSubKey(c => [c.z])
//OK!
//Expected: CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Error!
//Tooltip : noSubKey<readonly string[]>
noSubKey(c => [c.x])
//OK!
//Expected: CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Error!
//Tooltip : noSubKey<readonly string[]>
noSubKey(c => [c.x, c.y])

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSubKey<"z"[]>
noSubKey(() => ["z" as "z"]);
//OK!
//Expected: CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<["x"[], "is a sub key of", ("x" | "y")[]]>
//Tooltip : noSubKey<"x"[]>
noSubKey(() => ["x" as "x"]);
//OK!
//Expected: CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Tooltip : noSubKey<("x" | "y")[]>
noSubKey(() => ["x" as "x", "y" as "y"]);

declare function noSuperKey<KeyT extends Key> (
  arg : (
    ((c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT) &
    (
        FindSubKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        unknown :
        CompileError<[
            KeyT,
            "is a super key of",
            FindSubKey<
                (("x"|"y")[])[],
                KeyT
            >
        ]>
    )
  )
) : void

//Error!
//Expected: Infer KeyT as "z"[]
//Actual  : CompileError<[readonly string[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<readonly string[]>
noSuperKey(c => [c.z])
//Error!
//Expected: Infer KeyT as "x"[]
//Actual  : CompileError<[readonly string[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<readonly string[]>
noSuperKey(c => [c.x])
//Error!
//Expected: CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Actual  : CompileError<[readonly string[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<readonly string[]>
noSuperKey(c => [c.x, c.y])

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSuperKey<"z"[]>
noSuperKey(() => ["z" as "z"]);
//OK!
//Expected: Infer KeyT as "x"[]
//Actual  : Infer KeyT as "x"[]
//Tooltip : noSuperKey<"x"[]>
noSuperKey(() => ["x" as "x"]);
//OK!
//Expected: CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSuperKey<("x" | "y")[]>
noSuperKey(() => ["x" as "x", "y" as "y"]);

/**
 * Seems weird that using the `c` argument results in inference problems.
 * But using string literals without the `c` argument is okay.
 */

Expected behavior:

Whether I use the c argument or not when calling noSubKey()/noSuperKey(),
it should always infer the type of KeyT correctly for all cases.

Actual behavior:

noSubKey()

  • With c argument, it correctly infers KeyT in the error message

  • With c argument, it incorrectly infers KeyT in the tooltip

  • With string literals, it correctly infers KeyT in the error message

  • With string literals, it correctly infers KeyT in the tooltip

noSuperKey()

  • With c argument, it incorrectly infers KeyT in the error message

  • With c argument, it incorrectly infers KeyT in the tooltip

  • With string literals, it correctly infers KeyT in the error message

  • With string literals, it correctly infers KeyT in the tooltip

Playground Link:

Playground

Related Issues:

Off the top of my head, I've made similar reports before and other similar repro cases,

#29133

#23689 (comment)

From my personal experience, it feels like the moment you are using the ReturnType<> of a function in a conditional type (directly or indirectly) inside a parameter, you end up having weird issues with inference.

For the longest time, I've been trying to find a solid workaround but nothing seems to quite stick.

Every workaround I can come up with will work in some situation but not in others.


You'll notice that, in this repro, I never even use ReturnType<> on FunctionT.

I use KeyT and make the parameter (c) => KeyT and it works in some cases but breaks in this case for noSuperKey() and works (mostly) fine for noSubKey()

@AnyhowStep
Copy link
Contributor Author

What's extra weird to me is that c.x is literally (pun, ha) of the literal type "x".

But using "x" as "x" is somehow more "kosher".

@AnyhowStep
Copy link
Contributor Author

Also, if you move c outside of the function params... It works fine again,

export interface CompileError<_ErrorMessageT extends any[]> {
    /**
     * There should never be a value of this type
     */
    readonly __compileError : never;
}
/**
 * Each `string` element represents a column name.
 *
 * A "key" is a set of columns that uniquely identifies
 * a row in a table.
 */
export type Key = readonly string[];

export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
    A extends Key ?
    (
        B extends Key ?
        (
            A[number] extends B[number] ?
            A :
            never
        ) :
        never
    ) :
    never
);

export type FindSubKey<
    ArrT extends readonly Key[],
    KeyT extends Key
> = (
    ExtractSubKey<
        ArrT[number],
        KeyT
    >
);

/**
 * Moved `c` to a global variable.
 * `c` is no longer a function arg.
 */
declare const c : {
  x : "x",
  y : "y",
  z : "z"
};
declare function noSuperKey<KeyT extends Key> (
  arg : (
    (() => KeyT) &
    (
        FindSubKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        unknown :
        CompileError<[
            KeyT,
            "is a super key of",
            FindSubKey<
                (("x"|"y")[])[],
                KeyT
            >
        ]>
    )
  )
) : void

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSubKey<"z"[]>
noSuperKey(() => [c.z])
//OK!
//Expected: Infer KeyT as "x"[]
//Actual  : Infer KeyT as "x"[]
//Tooltip : noSubKey<"x"[]>
noSuperKey(() => [c.x])
//OK!
//Expected: CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSubKey<("x" | "y")[]>
noSuperKey(() => [c.x, c.y])

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSubKey<"z"[]>
noSuperKey(() => ["z" as "z"]);
//OK!
//Expected: Infer KeyT as "x"[]
//Actual  : Infer KeyT as "x"[]
//Tooltip : noSubKey<"x"[]>
noSuperKey(() => ["x" as "x"]);
//OK!
//Expected: CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a super key of", ("x" | "y")[]]>
//Tooltip : noSubKey<("x" | "y")[]>
noSuperKey(() => ["x" as "x", "y" as "y"]);

Playground

@AnyhowStep
Copy link
Contributor Author

So, "x" as "x" and global c.x is okay with inference.

Function arg c.x is bad for inference.

@AnyhowStep
Copy link
Contributor Author

In my particular use case, I'm writing a fluent API for a builder. Each method returns a new instance of the builder.

I'm using callback functions and checking the return type because there are some complicated constraints I need to enforce during compile time

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

Changing the types to,

export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
    A[number] extends B[number] ?
    A :
    never
);

export type ExtractSuperKey<
    A extends Key,
    B extends Key
> = (
    B[number] extends A[number] ?
    A :
    never
);

does not affect the results.

So, smaller repro,

Playground

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

It seems like moving the conditional type to the this param doesn't work either,

declare function noSuperKey<KeyT extends Key> (
  this : (
    FindSubKey<
        (("x"|"y")[])[],
        KeyT
    > extends never ?
    any :
    CompileError<[
        KeyT,
        "is a super key of",
        FindSubKey<
            (("x"|"y")[])[],
            KeyT
        >
    ]>
  ),
  arg : (
    ((c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT)
  )
) : void

Still works for noSubKey(), though

Playground


If you hover over the inferred type of the callback functions, it seems to "know" the correct type.

image

It's just that the conditional type inside the param and the type param gets all confused.

Adding an explicit cast to the type that TS already knows seems to "fix" the inference.

//OK!
//Expected: Infer KeyT as "z"[]
//Actual  : Infer KeyT as "z"[]
//Tooltip : noSuperKey<"z"[]>
//Inferred type of callback: (c: { x: "x", y: "y", z: "z" }) => "z"[]
noSuperKey(
  (c => [c.z]) as (
    //Add an explicit cast and it works
    (c: { x: "x", y: "y", z: "z" }) => "z"[]
  )
);

Playground

@AnyhowStep
Copy link
Contributor Author

It looks like the order of A and B matters in ExtractSubKey<> and ExtractSuperKey<>.

They are almost exactly the same; it's just that one is A[number] extends B[number], the other is B[number] extends A[number]

This gives problems,

A[number] extends B[number] ?

This does not, (not as much)

B[number] extends A[number] ?

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

Uhhhh..... Wat?

Changing the implementation of ExtractSubKey<> and ExtractSuperKey<> to this makes both work (reasonably) well,

//This works, I have no idea why.
export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
    A[number] extends Extract<B[number], A[number]> ?
    A :
    never
);

export type ExtractSuperKey<
    A extends Key,
    B extends Key
> = (
    B[number] extends Extract<A[number], B[number]> ?
    A :
    never
);

I think I'm going crazy because all I did was shift types around (out of desperation, lol) and it works now... And I have no idea why one implementation works better than the other.

Also, this implementation is waywayway more complicated than the original implementation.... But somehow works.


The definition of insanity is doing the same thing over and over again, but expecting different results.
-Not Albert Einstein but many people say he said it anyway

I only tried this convoluted implementation because I wanted to do something different.
I wanted to stir the primordial soup of types and see if anything would evolve and survive the harsh world of the TS inference mechanism.

I give this implementation my blessing. May it go forth and procreate.
It is the fittest of the implementations and may live in my codebase.


There are still problems with the tooltip and it concerns me.

//OK!
//Expected: CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Actual  : CompileError<[("x" | "y")[], "is a sub key of", ("x" | "y")[]]>
//Error!
//Tooltip : noSubKey<readonly string[]> <-- Expected ("x" | "y")[]
//Error!
//this    : any <-- Expected CompileError<>
noSubKey(c => [c.x, c.y])

image


Full repro and details comments about what is expected and what actually happens,

Playground

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

I re-introduced the conditional types in ExtractSubKey<> and ExtractSuperKey<> to distribute the types of A and B and it still works (apart from tooltips generating the wrong types in some cases),

export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
  A extends Key ?
  (
    B extends Key ?
    (
      A[number] extends Extract<B[number], A[number]> ?
      A :
      never
    ) :
    never
  ) :
  never
);

export type ExtractSuperKey<
    A extends Key,
    B extends Key
> = (
  A extends Key ?
  (
    B extends Key ?
    (
      B[number] extends Extract<A[number], B[number]> ?
      A :
      never
    ) :
    never
  ) :
  never
);

Playground


With this implementation,

noSubKey()

  • With c argument, it correctly infers KeyT in the error message

  • With c argument, it incorrectly infers KeyT in the tooltip

  • With string literals, it correctly infers KeyT in the error message

  • With string literals, it correctly infers KeyT in the tooltip

noSuperKey()

  • With c argument, it correctly infers KeyT in the error message

  • With c argument, it incorrectly infers KeyT in the tooltip

  • With string literals, it correctly infers KeyT in the error message

  • With string literals, it correctly infers KeyT in the tooltip

@AnyhowStep AnyhowStep changed the title Return type inference breaks in function parameter Return type inference breaks in function parameter; tooltip also inconsistent Jul 25, 2019
@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

I tried changing,

  • () => KeyT
  • KeyT extends Key

to,

  • D extends SomeDelegate
  • ReturnType<D>

It gives the same result as KeyT,
Playground


This is super weird to me because I have been under the impression that ReturnType<> is broken when used in function parameters for the longest time. I've even filed issues about it and mentioned it in comments elsewhere with examples of how it seems to be broken.

However, in my library, I could find examples of it working in some cases and not working in other cases. They were all such complicated examples that I could never find a small repro.

This is the first time I've ever actually found a decently small repro and found evidence that it isn't a ReturnType<DelegateT> vs. ReturnT problem. It's just that some generic types aren't inference-friendly.


As an anecdote, I had many attempts at (relatively) simple things using ReturnType<DelegateT> in function params and they failed really bad.

However, there was one single attempt that worked.

It was the type-level equivalent of,

for (let i=0; i<ArrT["length"]; ++i) {
    for (let j=i+1; j<ArrT["length"]; ++j) {
        /*snip complicated type using ArrT, i and j*/
    }
}

A type-level nested for-loop over a generic array type! And the actual type was an intersection of at least four other more complicated types.

I wanted to be consistent with the rest of my codebase and refactor it to not use ReturnType<DelegateT>. However, it was so complex and scary and worked that I didn't dare to touch it.

So, while everything else abandoned ReturnType<DelegateT> in function params, that one function continued to have it. It was the only thing that defied my intuition that ReturnType<DelegateT> in param = bad

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

I needed a version that was subKeyOnly() and superKeyOnly().

I tried a modified version what I posted above and... It doesn't work.

//THIS IS BROKEN
declare function subKeyOnly<KeyT extends Key> (
  this : (
    FindSuperKey<
        (("x"|"y")[])[],
        KeyT
    > extends never ?
    CompileError<[
        KeyT,
        "is NOT a sub key of",
        (("x"|"y")[])[]
    ]> :
    any
  ),
  arg : (
    (c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT 
  )
) : void
//THIS IS BROKEN
declare function superKeyOnly<KeyT extends Key> (
  this : (
    FindSubKey<
        (("x"|"y")[])[],
        KeyT
    > extends never ?
    CompileError<[
        KeyT,
        "is NOT a super key of",
        (("x"|"y")[])[]
    ]> :
    any
  ),
  arg : (
    ((c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT)
  )
) : void

Playground

As usual, string literals are okay, but using function args breaks.


While the inferred type of KeyT is broken by simply switching the types in the true and false branches of the conditional type, it seems that the tooltip display for the type of this is consistent, at least.

It shows CompileError<> instead of any

@AnyhowStep
Copy link
Contributor Author

By reverting back to this,

export type ExtractSubKey<
    A extends Key,
    B extends Key
> = (
    A[number] extends B[number] ?
    A :
    never
);

export type ExtractSuperKey<
    A extends Key,
    B extends Key
> = (
    B[number] extends A[number] ?
    A :
    never
);

I get,

//Broken
subKeyOnly(c => [c.z])
//Broken
subKeyOnly(c => [c.x])
//Broken
subKeyOnly(c => [c.x, c.y])

//OK!
subKeyOnly(() => ["z" as "z"]);
//OK!
subKeyOnly(() => ["x" as "x"]);
//OK!
subKeyOnly(() => ["x" as "x", "y" as "y"]);


//OK!
superKeyOnly(c => [c.z])
//OK!
superKeyOnly(c => [c.x])
//OK!
superKeyOnly(c => [c.x, c.y])

//OK!
superKeyOnly(() => ["z" as "z"]);
//OK!
superKeyOnly(() => ["x" as "x"]);
//OK!
superKeyOnly(() => ["x" as "x", "y" as "y"]);

Playground


Guess I gotta' figure out how to make it work for this particular case.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

It seems like this implementation of subKeyOnly() works,

declare function subKeyOnly<KeyT extends Key> (
  arg : (
    (c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT &
    (
        FindSuperKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        CompileError<[
            KeyT,
            "is not a sub key of",
            (("x"|"y")[])[]
        ]> :
        unknown
    )
  )
) : void

Playground


If I tweak it ever so slightly, it breaks,
(Can you spot the difference?)

Spoiler! The first snippet is,

(c : {/*snip*/}) => KeyT & (/*snip*/)

The second snippet is,

((c : {/*snip*/}) => KeyT) & (/*snip*/)

The difference is an extra parentheses.

declare function subKeyOnly<KeyT extends Key> (
  arg : (
    ((c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT) &
    (
        FindSuperKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        CompileError<[
            KeyT,
            "is not a sub key of",
            (("x"|"y")[])[]
        ]> :
        unknown
    )
  )
) : void

Playground

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

However, moving it to the this param breaks it,

declare function subKeyOnly<KeyT extends Key> (
  this : (
    FindSuperKey<
        (("x"|"y")[])[],
        KeyT
    > extends never ?
    CompileError<[
        KeyT,
        "is not a sub key of",
        (("x"|"y")[])[]
    ]> :
    unknown
  ),
  arg : (
    (c : {
      x : "x",
      y : "y",
      z : "z"
    }) => KeyT
  )
) : void

Playground


Moving the callback's type to the inside of the conditional type breaks it,

declare function subKeyOnly<KeyT extends Key> (
  arg : (
    (
        FindSuperKey<
            (("x"|"y")[])[],
            KeyT
        > extends never ?
        CompileError<[
            KeyT,
            "is not a sub key of",
            (("x"|"y")[])[]
        ]> :
        (c : {
            x : "x",
            y : "y",
            z : "z"
        }) => KeyT
    )
  )
) : void

Playground

@RyanCavanaugh
Copy link
Member

@AnyhowStep Please unassign yourself when you reach a conclusion and provide a more actionable summary if possible - thanks! 😁

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Jul 25, 2019
@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented Jul 25, 2019

Sorry for the wall of comments. I used it to track the myriad of different ways to do this one specific thing,

type PerformComplicatedCompileTimeChecks<FunctionT extends (...args:any[]) => any, T0, T1, /*snip*/> = (
  /*snip*/ extends /*snip*/ ?
  //success
  FunctionT : 
  //failure
  CompileError<["Some error message"]>
);
declare function foo<FunctionT extends (...args:any[]) => any> (
  f : PerformComplicatedCompileTimeChecks<FunctionT, T0, T1, /*snip*/>
) : void

//Assume this is OK
//Return type of `1` is fine
//Compiles fine.
//No squiggly
foo(() => 1);

//Assume this somehow breaks some custom compile-time constraint
//Return type of `2` violates the very basic principles of the universe
//Should not compile.
//Red squiggly saying, CompileError<["Some error message"]>
foo(() => 2);

There are many ways to implement the above. We basically need to test the ReturnType<> of FunctionT inside of foo's parameter list.

  • If ReturnType<> satisfies a compile-time check, we let f be FunctionT
  • If ReturnType<> does not satisfy a compile-time check, we let f be not FunctionT

However, I have not been able to find the one-true-way to implement the above.

It seems like, no matter what attempt I use, it will work for some cases but completely break for other cases. And it is never intuitive why it works/does not work.

Getting something like the above to work has involved a lot of trial and error.


Generally, my attempts break when FunctionT accepts arguments and the concrete type of FunctionT uses those arguments.

I don't know why, though.

This has also been noted in a similar issue I created before, and I never found out why.

Using variables from outside FunctionT or using literals works most of the time.


For all the cases above where I say "this breaks", it seems to me like they are bugs.

Because they're all expressing the same thing I want, just with types shuffled around in hopes of appeasing TS' return type inference =x


I think I've finally found my Neo. I have found The One.
#32540 (comment)

I believe the one-true-way for it to work is this,

type PerformComplicatedCompileTimeChecks<ReturnT, T0, T1, /*snip*/> = (
  ReturnT &
  (
    /*snip*/ extends /*snip*/ ?
    //success
    unknown : 
    //failure
    CompileError<["Some error message"]>
  )
);
declare function foo<ReturnT> (
  f : (/*snip args*/) => PerformComplicatedCompileTimeChecks<
    ReturnT, T0, T1, /*snip*/
  >
) : void

//Assume this is OK
//Return type of `1` is fine
//Compiles fine.
//No squiggly
foo(() => 1);

//Assume this somehow breaks some custom compile-time constraint
//Return type of `2` violates the very basic principles of the universe
//Should not compile.
//Red squiggly saying, 2 is not assignable to `2 & CompileError<["Some error message"]>`
foo(() => 2);
  • If ReturnT satisfies a compile-time check, we let the return type be ReturnT & unknown = ReturnT
  • If ReturnT does not satisfy a compile-time check, we let the return type be ReturnT & CompileError

However, I will need to test more with my code base.

When he is ready, I shall take him to see you (The Oracle)


And if he is The One, I'd like to know how he came to be The One

But I would like to know if all the other failures are bugs, or design limitations, or by design.

@AnyhowStep
Copy link
Contributor Author

After further experimentation, it seems like even my one-true-way attempt isn't perfect.
It works in most cases.
Except when you need to use the return type to create another type.

I'm going to call this other type a correlated type. Because it reminds me of a correlated subquery from SQL.


This comment shows that the supposed one-true-way (using ReturnT) fails when used with a correlated type,
#14829 (comment)

@RyanCavanaugh
Copy link
Member

Please log a new issue if any action is required on our side. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs More Info The issue still hasn't been fully clarified
Projects
None yet
Development

No branches or pull requests

2 participants