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

Need to cast string as string literal #25889

Closed
Akxe opened this issue Jul 24, 2018 · 16 comments
Closed

Need to cast string as string literal #25889

Akxe opened this issue Jul 24, 2018 · 16 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@Akxe
Copy link

Akxe commented Jul 24, 2018

TypeScript Version: 3.1.0-dev.201xxxxx

Search Terms:
string as string, cast string as string, cast string as string literal
I am sorry, I have no idea on how to search for it, enlighten me, if you know.

Code

interface Iperiod {
	id: number | 'current';
	name: string;
}

function usePeriods(periods: Iperiod[]) {
	return periods;
}

usePeriods(['now', 'then'].map((name, index) => {
	if (name == 'now') {
		return {
			name,
			id: 'current',
		}
	}
	
	return {
		name,
		id: index,
	}
}));

Expected behavior:
It should work, as id can be number | 'current'.

Actual behavior:
Error string is not assignable to 'current'.

Playground Link:
link

Related Issues:
No, but I suppose a few.

@j-oliveras
Copy link
Contributor

If you declares the return type of the lambda it works:

interface Iperiod {
	id: number | 'current';
	name: string;
}

function usePeriods(periods: Iperiod[]) {
	return periods;
}

usePeriods(['now', 'then'].map((name, index): Iperiod => {
	if (name == 'now') {
		return {
			name,
			id: 'current',
		}
	}
	
	return {
		name,
		id: index,
	}
}));

I think is a duplicate.

@Akxe
Copy link
Author

Akxe commented Jul 24, 2018

If I cast it like so it works:

return {
	name,
	id: 'current' as 'current',
}

, but damn, it feels dumb...

@ghost
Copy link

ghost commented Jul 24, 2018

There are actually a few reasons why you should ensure an object literal has a contextual type -- it helps avoid misspelling optional properties and gets you literal types. (See palantir/tslint#2581)

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Jul 24, 2018
@Akxe
Copy link
Author

Akxe commented Jul 28, 2018

@j-oliveras But the usePeriods function requires Iperiod[], therefor this information should be passed down to the map method return type, don't you think?

@ghost
Copy link

ghost commented Aug 13, 2018

Created an issue to improve error reporting here: #26413

@Akxe
Copy link
Author

Akxe commented Sep 24, 2018

Could you please elaborate more on how this helps "avoid misspelling optional properties and gets you literal types"? Perhaps adding example, that says thousands words.
If this should be sometime closed it should bet a proper explanation of what is that big benefit that we have to cast string literal to string literals.

@ghost
Copy link

ghost commented Sep 24, 2018

The "it" in that sentence reverse to "has a contextual type", which comes from giving a type annotation to the object. That also prevents you from needing to cast string literals.

interface I {
    kind: "a" | "b",
    optional?: number
}
declare function f(i: I): void;

// No contextual type
const i0 = { kind: "a" };
f(i0); // fails

const i1 = { kind: "a" as "a", optinal: 3 };
f(i1); // No compile error, but "optional" not specified as intended

f({ kind: "a", optional: 3 }); // Perfect!
f({ kind: "a", optinal: 3 }); // Compiler catches "optinal" spelling error

@Akxe
Copy link
Author

Akxe commented Sep 24, 2018

I still have hard time understanding it. Or at least to me it is almost like

f({ kind: "a", optinal: 3 });
f({ kind: "a" as "a", optinal: 3 });

I don't see why, if the string literal is in returned object is treated differently from one constructed in parameter.

@RyanCavanaugh
Copy link
Member

@Akxe the effective question is, let's say you write this

const obj = { kind: "a" };
obj.kind = "b";

Is this code valid?

When you write an expression, TS has to guess what the domain you intended was. We use the same inference for determining how it's legal to use the object both for read and for writes -- obj.kind is inferred to be string unless specified otherwise, which means that the assignment of "b" is legal but that we aren't guaranteeing it's always "a" (thus the error).

@Akxe
Copy link
Author

Akxe commented Sep 24, 2018

I get that point, but consider this

interface I {
    code: 'mapped',
    name: string,
}

const a: I[] = ['a', 'b'].map(name => {
    return {
        code: 'mapped',
        name,
    }
});

Here the error is definitely unnecessary, as original will be always of type 'mapped'.

@ghost
Copy link

ghost commented Sep 24, 2018

@Akxe That's a bug: #11152

@Akxe
Copy link
Author

Akxe commented Sep 24, 2018

It is 2 years old... Any info on what it is waiting for? I saw there waiting for proposal, but how to make one, is there a template?

@typescript-bot
Copy link
Collaborator

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

@sekoyo
Copy link

sekoyo commented Dec 29, 2019

'something' as 'something' seems verbose and pointless? TS should simply error if they didn't pass one of the string literals defined. I don't want my users to have to import a certain type they should just be able to pass a string that is type checked

@IlyaSemenov
Copy link

Note: 'something' as const is a bit less dumb but works the same.

allgreed added a commit to allgreed/lmap that referenced this issue May 16, 2020
Previously the Resource typing was awkward at best - asserting Resources
to concrete types seamed like needles work that defied the point of
having them defined as interfaces

One potential solution was asserting string literal to string literal,
but that felt backwards, example: "literal" as "literal"

Someone in the Github thread[0] suggested asserting them in the
following manner: "literal" as const - which is a bit better, but still
misses the point

Due to technical reasons the literal inferred type was string, rather
than the literal itself. What seams to solve the problem is asserting
the whole object to be a Resource. That also solves the problem with
creeping import scope while at the same time setting up correct guards
at compile time

Last, but not least I think that the documentation on type assertion is
misleading at best (couldn't find an example describing the mechanism
and didn't feel like going through the source) - the mechanism is much
more safe than described in the official docs, which cover the case of
any -> T. Whereas in the more general case of T -> U assertion the
overlap is checked (so that if I make a typo in the object an error is
raised)

[0] microsoft/TypeScript#25889
allgreed added a commit to allgreed/lmap that referenced this issue May 16, 2020
Previously the Resource typing was awkward at best - asserting Resources
to concrete types seamed like needles work that defied the point of
having them defined as interfaces

One potential solution was asserting string literal to string literal,
but that felt backwards, example: "literal" as "literal"

Someone in the Github thread[0] suggested asserting them in the
following manner: "literal" as const - which is a bit better, but still
misses the point

Due to technical reasons the literal inferred type was string, rather
than the literal itself. What seams to solve the problem is asserting
the whole object to be a Resource. That also solves the problem with
creeping import scope while at the same time setting up correct guards
at compile time

Last, but not least I think that the documentation on type assertion is
misleading at best (couldn't find an example describing the mechanism
and didn't feel like going through the source) - the mechanism is much
more safe than described in the official docs, which cover the case of
any -> T. Whereas in the more general case of T -> U assertion the
overlap is checked (so that if I make a typo in the object an error is
raised)

[0] microsoft/TypeScript#25889
dgliwka pushed a commit to allgreed/lmap that referenced this issue May 16, 2020
Previously the Resource typing was awkward at best - asserting Resources
to concrete types seamed like needles work that defied the point of
having them defined as interfaces

One potential solution was asserting string literal to string literal,
but that felt backwards, example: "literal" as "literal"

Someone in the Github thread[0] suggested asserting them in the
following manner: "literal" as const - which is a bit better, but still
misses the point

Due to technical reasons the literal inferred type was string, rather
than the literal itself. What seams to solve the problem is asserting
the whole object to be a Resource. That also solves the problem with
creeping import scope while at the same time setting up correct guards
at compile time

Last, but not least I think that the documentation on type assertion is
misleading at best (couldn't find an example describing the mechanism
and didn't feel like going through the source) - the mechanism is much
more safe than described in the official docs, which cover the case of
any -> T. Whereas in the more general case of T -> U assertion the
overlap is checked (so that if I make a typo in the object an error is
raised)

[0] microsoft/TypeScript#25889
@pstephenwille
Copy link

Was there a reason this was closed? I seemed to have missed it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

8 participants