how can I express that a parameter can extend a list of strings in typescript?












3














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?










share|improve this question
























  • If you can pass in any string how is it different from string?
    – Titian Cernicova-Dragomir
    Nov 19 '18 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 '18 at 21:45










  • Possible duplicate of Extending union type alias in typescript?
    – jcalz
    Nov 20 '18 at 0:32
















3














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?










share|improve this question
























  • If you can pass in any string how is it different from string?
    – Titian Cernicova-Dragomir
    Nov 19 '18 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 '18 at 21:45










  • Possible duplicate of Extending union type alias in typescript?
    – jcalz
    Nov 20 '18 at 0:32














3












3








3







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?










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 20 '18 at 5:07









B12Toaster

1,96311324




1,96311324










asked Nov 19 '18 at 21:01









Eric LiprandiEric Liprandi

2,23112637




2,23112637












  • If you can pass in any string how is it different from string?
    – Titian Cernicova-Dragomir
    Nov 19 '18 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 '18 at 21:45










  • Possible duplicate of Extending union type alias in typescript?
    – jcalz
    Nov 20 '18 at 0:32


















  • If you can pass in any string how is it different from string?
    – Titian Cernicova-Dragomir
    Nov 19 '18 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 '18 at 21:45










  • Possible duplicate of Extending union type alias in typescript?
    – jcalz
    Nov 20 '18 at 0:32
















If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 '18 at 21:06




If you can pass in any string how is it different from string?
– Titian Cernicova-Dragomir
Nov 19 '18 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 '18 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 '18 at 21:45












Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 '18 at 0:32




Possible duplicate of Extending union type alias in typescript?
– jcalz
Nov 20 '18 at 0:32












1 Answer
1






active

oldest

votes


















2














@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');






share|improve this answer



















  • 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 '18 at 0:38






  • 1




    @jcalz yeah, I agree with you...
    – B12Toaster
    Nov 20 '18 at 0:42











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















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

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









2














@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');






share|improve this answer



















  • 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 '18 at 0:38






  • 1




    @jcalz yeah, I agree with you...
    – B12Toaster
    Nov 20 '18 at 0:42
















2














@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');






share|improve this answer



















  • 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 '18 at 0:38






  • 1




    @jcalz yeah, I agree with you...
    – B12Toaster
    Nov 20 '18 at 0:42














2












2








2






@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');






share|improve this answer














@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');







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 20 '18 at 7:58

























answered Nov 19 '18 at 21:22









B12ToasterB12Toaster

1,96311324




1,96311324








  • 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 '18 at 0:38






  • 1




    @jcalz yeah, I agree with you...
    – B12Toaster
    Nov 20 '18 at 0:42














  • 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 '18 at 0:38






  • 1




    @jcalz yeah, I agree with you...
    – B12Toaster
    Nov 20 '18 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 '18 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 '18 at 0:38




1




1




@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 '18 at 0:42




@jcalz yeah, I agree with you...
– B12Toaster
Nov 20 '18 at 0:42


















draft saved

draft discarded




















































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.




draft saved


draft discarded














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





















































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







Popular posts from this blog

Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

Does disintegrating a polymorphed enemy still kill it after the 2018 errata?

A Topological Invariant for $pi_3(U(n))$