how can I express that a parameter can extend a list of strings in typescript?
up vote
3
down vote
favorite
I have a method that takes a string
parameter. However, I want to constrain that parameter a little bit. So, I create a union type with the strings I want to accept:
type Foo = 'a' | 'b';
now, I have a simple method:
function bar(foo: Foo) {
// do something with that string
}
What I want to do is allow a caller to pass other strings (besides a
and b
). However, I want them to sort of extend the original union type.
function bar<T extends Foo>(str: T) {
// do something with that string
}
type Bar = Foo | 'c';
// this doesn't work: Argument of type 'Bar' is not assignable to parameter of type 'Foo'. Type '"c"' is not assignable to type 'Foo'.
bar('c' as Bar);
Is there any way to express this constraint in TypeScript?
typescript
add a comment |
up vote
3
down vote
favorite
I have a method that takes a string
parameter. However, I want to constrain that parameter a little bit. So, I create a union type with the strings I want to accept:
type Foo = 'a' | 'b';
now, I have a simple method:
function bar(foo: Foo) {
// do something with that string
}
What I want to do is allow a caller to pass other strings (besides a
and b
). However, I want them to sort of extend the original union type.
function bar<T extends Foo>(str: T) {
// do something with that string
}
type Bar = Foo | 'c';
// this doesn't work: Argument of type 'Bar' is not assignable to parameter of type 'Foo'. Type '"c"' is not assignable to type 'Foo'.
bar('c' as Bar);
Is there any way to express this constraint in TypeScript?
typescript
If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 at 21:06
1
@TitianCernicova-Dragomir in the OP he said "other", not "any". He wants to extend the set of allowed strings.
– B12Toaster
Nov 19 at 21:45
Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 at 0:32
add a comment |
up vote
3
down vote
favorite
up vote
3
down vote
favorite
I have a method that takes a string
parameter. However, I want to constrain that parameter a little bit. So, I create a union type with the strings I want to accept:
type Foo = 'a' | 'b';
now, I have a simple method:
function bar(foo: Foo) {
// do something with that string
}
What I want to do is allow a caller to pass other strings (besides a
and b
). However, I want them to sort of extend the original union type.
function bar<T extends Foo>(str: T) {
// do something with that string
}
type Bar = Foo | 'c';
// this doesn't work: Argument of type 'Bar' is not assignable to parameter of type 'Foo'. Type '"c"' is not assignable to type 'Foo'.
bar('c' as Bar);
Is there any way to express this constraint in TypeScript?
typescript
I have a method that takes a string
parameter. However, I want to constrain that parameter a little bit. So, I create a union type with the strings I want to accept:
type Foo = 'a' | 'b';
now, I have a simple method:
function bar(foo: Foo) {
// do something with that string
}
What I want to do is allow a caller to pass other strings (besides a
and b
). However, I want them to sort of extend the original union type.
function bar<T extends Foo>(str: T) {
// do something with that string
}
type Bar = Foo | 'c';
// this doesn't work: Argument of type 'Bar' is not assignable to parameter of type 'Foo'. Type '"c"' is not assignable to type 'Foo'.
bar('c' as Bar);
Is there any way to express this constraint in TypeScript?
typescript
typescript
edited Nov 20 at 5:07
B12Toaster
1,52811222
1,52811222
asked Nov 19 at 21:01
Eric Liprandi
2,18712637
2,18712637
If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 at 21:06
1
@TitianCernicova-Dragomir in the OP he said "other", not "any". He wants to extend the set of allowed strings.
– B12Toaster
Nov 19 at 21:45
Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 at 0:32
add a comment |
If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 at 21:06
1
@TitianCernicova-Dragomir in the OP he said "other", not "any". He wants to extend the set of allowed strings.
– B12Toaster
Nov 19 at 21:45
Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 at 0:32
If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 at 21:06
If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 at 21:06
1
1
@TitianCernicova-Dragomir in the OP he said "other", not "any". He wants to extend the set of allowed strings.
– B12Toaster
Nov 19 at 21:45
@TitianCernicova-Dragomir in the OP he said "other", not "any". He wants to extend the set of allowed strings.
– B12Toaster
Nov 19 at 21:45
Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 at 0:32
Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 at 0:32
add a comment |
1 Answer
1
active
oldest
votes
up vote
2
down vote
@jscalz 's comment made me reconsider my answer.
From a classical OOP perspective, the extends
constraint in bar<T extends Foo>(arg: T)
on the generic parameter would mean that T
possesses all properties of Foo
and more. So with this constraint you can safely assume in your bar
that arg
behaves at least like Foo
(and has the same property structure).
However, in your case, T
is neither a class
or an interface
but a union of string literals and this is where the extends
gets pointless:
Let's assume bar<T extends Foo>(arg: T)
worked as you want it to (as stated in your OP), meaning, T
is a superset of string literals on top of Foo
. This would mean that in your bar()
function you would have no control at all over which values arg
holds – it may hold any kind of values and you might as well use any.
Given the above, your only chance to control what makes it's way into bar()
is to use function bar(args: Bar)
, where Bar = Foo | 'c'
which then also allows you to call bar('c');
without casting.
Original answer:
Defining type Bar
as intersection type seems to at least tame the typescript linting errors: type Bar = Foo & 'c';
.
Below I list the relevant parts (at least to my understanding) from the typescript spec that define that an intersection of types actually leads to a "Subtype-Supertype" relationship which is recognized by the extends
constraint:
In 3.11.3 Subtypes and Supertypes it says:
S is a subtype of a type T, and T is a supertype of S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is a subtype of T.
Moreover in 3.11.4 Assignment Compatibility (made bold by me):
S is assignable to a type T, and T is assignable from S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is assignable to T.
With regards to the extends
keyword, the Typescript handbook states the following (made bold by me):
For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the implements and extends clauses.
In your case Foo
equates to S
in the spec and Bar
is your supertype (or superset) which equates to T
spec.
However, I am not sure if this answer satisfies you because you can also write bar('x' as Bar);
and the compiler won't show an error despite 'x' not being included in your types.
I think your best shot is to simply use function bar(x: Bar)
. Which then also allows you to call bar('c');
1
Foo & 'c'
is equivalent tonever
(no string can be both"c"
and also either"a"
or"b"
). It's unlikely that is the intent here. More likely, the question is usingextends
incorrectly; instead ofT extends Foo
, the intent is more likeT super Foo
, but that doesn't exist in TypeScript.
– jcalz
Nov 20 at 0:38
1
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
@jscalz 's comment made me reconsider my answer.
From a classical OOP perspective, the extends
constraint in bar<T extends Foo>(arg: T)
on the generic parameter would mean that T
possesses all properties of Foo
and more. So with this constraint you can safely assume in your bar
that arg
behaves at least like Foo
(and has the same property structure).
However, in your case, T
is neither a class
or an interface
but a union of string literals and this is where the extends
gets pointless:
Let's assume bar<T extends Foo>(arg: T)
worked as you want it to (as stated in your OP), meaning, T
is a superset of string literals on top of Foo
. This would mean that in your bar()
function you would have no control at all over which values arg
holds – it may hold any kind of values and you might as well use any.
Given the above, your only chance to control what makes it's way into bar()
is to use function bar(args: Bar)
, where Bar = Foo | 'c'
which then also allows you to call bar('c');
without casting.
Original answer:
Defining type Bar
as intersection type seems to at least tame the typescript linting errors: type Bar = Foo & 'c';
.
Below I list the relevant parts (at least to my understanding) from the typescript spec that define that an intersection of types actually leads to a "Subtype-Supertype" relationship which is recognized by the extends
constraint:
In 3.11.3 Subtypes and Supertypes it says:
S is a subtype of a type T, and T is a supertype of S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is a subtype of T.
Moreover in 3.11.4 Assignment Compatibility (made bold by me):
S is assignable to a type T, and T is assignable from S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is assignable to T.
With regards to the extends
keyword, the Typescript handbook states the following (made bold by me):
For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the implements and extends clauses.
In your case Foo
equates to S
in the spec and Bar
is your supertype (or superset) which equates to T
spec.
However, I am not sure if this answer satisfies you because you can also write bar('x' as Bar);
and the compiler won't show an error despite 'x' not being included in your types.
I think your best shot is to simply use function bar(x: Bar)
. Which then also allows you to call bar('c');
1
Foo & 'c'
is equivalent tonever
(no string can be both"c"
and also either"a"
or"b"
). It's unlikely that is the intent here. More likely, the question is usingextends
incorrectly; instead ofT extends Foo
, the intent is more likeT super Foo
, but that doesn't exist in TypeScript.
– jcalz
Nov 20 at 0:38
1
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
add a comment |
up vote
2
down vote
@jscalz 's comment made me reconsider my answer.
From a classical OOP perspective, the extends
constraint in bar<T extends Foo>(arg: T)
on the generic parameter would mean that T
possesses all properties of Foo
and more. So with this constraint you can safely assume in your bar
that arg
behaves at least like Foo
(and has the same property structure).
However, in your case, T
is neither a class
or an interface
but a union of string literals and this is where the extends
gets pointless:
Let's assume bar<T extends Foo>(arg: T)
worked as you want it to (as stated in your OP), meaning, T
is a superset of string literals on top of Foo
. This would mean that in your bar()
function you would have no control at all over which values arg
holds – it may hold any kind of values and you might as well use any.
Given the above, your only chance to control what makes it's way into bar()
is to use function bar(args: Bar)
, where Bar = Foo | 'c'
which then also allows you to call bar('c');
without casting.
Original answer:
Defining type Bar
as intersection type seems to at least tame the typescript linting errors: type Bar = Foo & 'c';
.
Below I list the relevant parts (at least to my understanding) from the typescript spec that define that an intersection of types actually leads to a "Subtype-Supertype" relationship which is recognized by the extends
constraint:
In 3.11.3 Subtypes and Supertypes it says:
S is a subtype of a type T, and T is a supertype of S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is a subtype of T.
Moreover in 3.11.4 Assignment Compatibility (made bold by me):
S is assignable to a type T, and T is assignable from S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is assignable to T.
With regards to the extends
keyword, the Typescript handbook states the following (made bold by me):
For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the implements and extends clauses.
In your case Foo
equates to S
in the spec and Bar
is your supertype (or superset) which equates to T
spec.
However, I am not sure if this answer satisfies you because you can also write bar('x' as Bar);
and the compiler won't show an error despite 'x' not being included in your types.
I think your best shot is to simply use function bar(x: Bar)
. Which then also allows you to call bar('c');
1
Foo & 'c'
is equivalent tonever
(no string can be both"c"
and also either"a"
or"b"
). It's unlikely that is the intent here. More likely, the question is usingextends
incorrectly; instead ofT extends Foo
, the intent is more likeT super Foo
, but that doesn't exist in TypeScript.
– jcalz
Nov 20 at 0:38
1
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
add a comment |
up vote
2
down vote
up vote
2
down vote
@jscalz 's comment made me reconsider my answer.
From a classical OOP perspective, the extends
constraint in bar<T extends Foo>(arg: T)
on the generic parameter would mean that T
possesses all properties of Foo
and more. So with this constraint you can safely assume in your bar
that arg
behaves at least like Foo
(and has the same property structure).
However, in your case, T
is neither a class
or an interface
but a union of string literals and this is where the extends
gets pointless:
Let's assume bar<T extends Foo>(arg: T)
worked as you want it to (as stated in your OP), meaning, T
is a superset of string literals on top of Foo
. This would mean that in your bar()
function you would have no control at all over which values arg
holds – it may hold any kind of values and you might as well use any.
Given the above, your only chance to control what makes it's way into bar()
is to use function bar(args: Bar)
, where Bar = Foo | 'c'
which then also allows you to call bar('c');
without casting.
Original answer:
Defining type Bar
as intersection type seems to at least tame the typescript linting errors: type Bar = Foo & 'c';
.
Below I list the relevant parts (at least to my understanding) from the typescript spec that define that an intersection of types actually leads to a "Subtype-Supertype" relationship which is recognized by the extends
constraint:
In 3.11.3 Subtypes and Supertypes it says:
S is a subtype of a type T, and T is a supertype of S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is a subtype of T.
Moreover in 3.11.4 Assignment Compatibility (made bold by me):
S is assignable to a type T, and T is assignable from S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is assignable to T.
With regards to the extends
keyword, the Typescript handbook states the following (made bold by me):
For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the implements and extends clauses.
In your case Foo
equates to S
in the spec and Bar
is your supertype (or superset) which equates to T
spec.
However, I am not sure if this answer satisfies you because you can also write bar('x' as Bar);
and the compiler won't show an error despite 'x' not being included in your types.
I think your best shot is to simply use function bar(x: Bar)
. Which then also allows you to call bar('c');
@jscalz 's comment made me reconsider my answer.
From a classical OOP perspective, the extends
constraint in bar<T extends Foo>(arg: T)
on the generic parameter would mean that T
possesses all properties of Foo
and more. So with this constraint you can safely assume in your bar
that arg
behaves at least like Foo
(and has the same property structure).
However, in your case, T
is neither a class
or an interface
but a union of string literals and this is where the extends
gets pointless:
Let's assume bar<T extends Foo>(arg: T)
worked as you want it to (as stated in your OP), meaning, T
is a superset of string literals on top of Foo
. This would mean that in your bar()
function you would have no control at all over which values arg
holds – it may hold any kind of values and you might as well use any.
Given the above, your only chance to control what makes it's way into bar()
is to use function bar(args: Bar)
, where Bar = Foo | 'c'
which then also allows you to call bar('c');
without casting.
Original answer:
Defining type Bar
as intersection type seems to at least tame the typescript linting errors: type Bar = Foo & 'c';
.
Below I list the relevant parts (at least to my understanding) from the typescript spec that define that an intersection of types actually leads to a "Subtype-Supertype" relationship which is recognized by the extends
constraint:
In 3.11.3 Subtypes and Supertypes it says:
S is a subtype of a type T, and T is a supertype of S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is a subtype of T.
Moreover in 3.11.4 Assignment Compatibility (made bold by me):
S is assignable to a type T, and T is assignable from S, if S has no excess properties with respect to T (3.11.5) and one of the following is true:
[...]
- S is a union type and each constituent type of S is assignable to T.
With regards to the extends
keyword, the Typescript handbook states the following (made bold by me):
For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the implements and extends clauses.
In your case Foo
equates to S
in the spec and Bar
is your supertype (or superset) which equates to T
spec.
However, I am not sure if this answer satisfies you because you can also write bar('x' as Bar);
and the compiler won't show an error despite 'x' not being included in your types.
I think your best shot is to simply use function bar(x: Bar)
. Which then also allows you to call bar('c');
edited Nov 20 at 7:58
answered Nov 19 at 21:22
B12Toaster
1,52811222
1,52811222
1
Foo & 'c'
is equivalent tonever
(no string can be both"c"
and also either"a"
or"b"
). It's unlikely that is the intent here. More likely, the question is usingextends
incorrectly; instead ofT extends Foo
, the intent is more likeT super Foo
, but that doesn't exist in TypeScript.
– jcalz
Nov 20 at 0:38
1
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
add a comment |
1
Foo & 'c'
is equivalent tonever
(no string can be both"c"
and also either"a"
or"b"
). It's unlikely that is the intent here. More likely, the question is usingextends
incorrectly; instead ofT extends Foo
, the intent is more likeT super Foo
, but that doesn't exist in TypeScript.
– jcalz
Nov 20 at 0:38
1
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
1
1
Foo & 'c'
is equivalent to never
(no string can be both "c"
and also either "a"
or "b"
). It's unlikely that is the intent here. More likely, the question is using extends
incorrectly; instead of T extends Foo
, the intent is more like T super Foo
, but that doesn't exist in TypeScript.– jcalz
Nov 20 at 0:38
Foo & 'c'
is equivalent to never
(no string can be both "c"
and also either "a"
or "b"
). It's unlikely that is the intent here. More likely, the question is using extends
incorrectly; instead of T extends Foo
, the intent is more like T super Foo
, but that doesn't exist in TypeScript.– jcalz
Nov 20 at 0:38
1
1
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 at 0:42
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53382554%2fhow-can-i-express-that-a-parameter-can-extend-a-list-of-strings-in-typescript%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 at 21:06
1
@TitianCernicova-Dragomir in the OP he said "other", not "any". He wants to extend the set of allowed strings.
– B12Toaster
Nov 19 at 21:45
Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 at 0:32