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

Type parameters as constraints #5949

Merged
merged 12 commits into from
Dec 11, 2015
Merged

Type parameters as constraints #5949

merged 12 commits into from
Dec 11, 2015

Conversation

ahejlsberg
Copy link
Member

With this PR it becomes possible for a type parameter constraint to reference type parameters from the same type parameter list (this was previously an error). For example:

function assign<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = source[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 });  // Error

A fancy term for this capability is F-Bounded Polymorphism.

Fixes #2304.

context.inferences[i].isFixed = true;
return getInferredType(context, i);
function getInferenceMapper(context: InferenceContext): TypeMapper {
if (!context.mapper) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invert the condition to reduce nesting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. There's a shared return statement at the bottom of the function and no else clause that would be reversed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to just repeat the return statement, but it's not really a big deal.

@ahejlsberg
Copy link
Member Author

@mhegazy Want to comment on this one before I merge it?

@mhegazy
Copy link
Contributor

mhegazy commented Dec 9, 2015

👍

@DanielRosenwasser
Copy link
Member

@ahejlsberg I tried this out and upon grabbing quick info on a, I'm seeing an uninstantiated type:

interface Mapper<T> {
    map<U extends T, V extends U[]>(f: (item: T) => U): V;
}

var m: Mapper<string>;
var a = m.map((x: string) => x);

image

I expected a to have the type string[], so something isn't working out here.

@ahejlsberg
Copy link
Member Author

@DanielRosenwasser Good catch. The problem is that the constraints aren't getting properly instantiated. Will put up a fix shortly.

@DanielRosenwasser
Copy link
Member

Hey @ahejlsberg, @Aleksey-Bykov wrote a useful example on #6037 which demonstrates the use of type parameters being able to extend each other. Can we add the following as a test case?

function fold<a, r>(values: a[], result: r, fold: (result: r, value: a) => r): r {
    for (let value of values) {
        result = fold(result, value);
    }
    return result;
}

function append<a, b extends a>(values: a[], value: b): a[] {
    values.push(value);
    return values;
}


fold(
    [1, 2, 3],
    [] as [string, string][],
    (result, value) => append(
        result,
        ["", ""]
    )
);

@zpdDG4gta8XKpMCd
Copy link

by the way

// test.ts
export function append<a, b extends a>(result: a[], value: b): a[] {
    result.push(value);
    return result;
}

compiles with

node ./built/local/tsc.js --module commonjs test.ts 

but fails with

node ./built/local/tsc.js --module commonjs --declaration test.ts 

saying

test.ts(1,37): error TS4016: Type parameter 'b' of exported function has or is using private name 'a'.

@zpdDG4gta8XKpMCd
Copy link

created a separate bug: #6040

@DanielRosenwasser
Copy link
Member

I noticed the same thing on my bug where the type parameter was not being instantiated. Can you enable // @declaration: true for all these test cases?

@ahejlsberg
Copy link
Member Author

Latest commits fix the uninstantiated type issue as well as issue in #6040.

@basarat
Copy link
Contributor

basarat commented Dec 10, 2015

@DanielRosenwasser
Copy link
Member

@ahejlsberg I think it wouldn't be unreasonable to just get declaration emit for all of these tests.

ahejlsberg added a commit that referenced this pull request Dec 11, 2015
@ahejlsberg ahejlsberg merged commit ff78477 into master Dec 11, 2015
@ahejlsberg ahejlsberg deleted the typeParametersAsConstraints branch December 11, 2015 00:54
@zpdDG4gta8XKpMCd
Copy link

@basarat, a poorman's implementation of this feature could have been done by passing
a function that maps B to A thus enforcing the constraint, consider:

foo <a, b extends a>(foo: a, bar:b): void;

vs

foo <a, b>(foo: a, bar: b, bIsA: (val: b) => a): void;

@dead-claudia
Copy link

Question: does this patch support the following? I've come across this pattern way too frequently, and I've needed it for way too many things. In languages that support it, it's a very frequent idiom.

interface Type<T extends Type<T>> {}

@ahejlsberg
Copy link
Member Author

@isiahmeadows Yes, that pattern (the "curiously recurring template pattern") is now supported. But also note that the polymorphic 'this' type is a more succinct way to accomplish the same thing.

@dead-claudia
Copy link

I also came across an unusual use case where I needed two F-bounded type
parameters, which the this type doesn't cover. Given this supports that
as well, I'm happy.

On Thu, Dec 17, 2015, 09:31 Anders Hejlsberg notifications@github.com
wrote:

@isiahmeadows https://github.com/isiahmeadows Yes, that pattern (the
"curiously recurring template pattern") is now supported. But also note
that the polymorphic 'this' type
#4910 is a more succinct
way to accomplish the same thing.


Reply to this email directly or view it on GitHub
#5949 (comment)
.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants