Accessing Attributes on Literals Work on All Types, But Not 'Int'; Why

Accessing attributes on literals work on all types, but not `int`; why?

So you think you can  dance  floating-point?

123 is just as much of an object as 3.14, the "problem" lies within the grammar rules of the language; the parser thinks we are about to define a float — not an int with a trailing method call.

We will get the expected behavior if we wrap the number in parenthesis, as in the below.

>>> (123).__str__()
'123'

Or if we simply add some whitespace after 123:

>>> 123 .__str__()
'123'


The reason it does not work for 123.__str__() is that the dot following the 123 is interpreted as the decimal-point of some partially declared floating-point.

>>> 123.__str__()
File "", line 1
123.__str__()
^
SyntaxError: invalid syntax

The parser tries to interpret __str__() as a sequence of digits, but obviously fails — and we get a SyntaxError basically saying that the parser stumbled upon something that it did not expect.



Elaboration

When looking at 123.__str__() the python parser could use either 3 characters and interpret these 3 characters as an integer, or it could use 4 characters and interpret these as the start of a floating-point.

123.__str__()
^^^ - int
123.__str__()
^^^^- start of floating-point

Just as a little child would like as much cake as possible on their plate, the parser is greedy and would like to swallow as much as it can all at once — even if this isn't always the best of ideas —as such the latter ("better") alternative is chosen.

When it later realizes that __str__() can in no way be interpreted as the decimals of a floating-point it is already too late; SyntaxError.

Note

 123 .__str__() # works fine

In the above snippet, 123  (note the space) must be interpreted as an integer since no number can contain spaces. This means that it is semantically equivalent to (123).__str__().

Note

 123..__str__() # works fine

The above also works because a number can contain at most one decimal-point, meaning that it is equivalent to (123.).__str__().



For the language-lawyers

This section contains the lexical definition of the relevant literals.

Lexical analysis - 2.4.5 Floating point literals

floatnumber   ::=  pointfloat | exponentfloat
pointfloat ::= [intpart] fraction | intpart "."
exponentfloat ::= (intpart | pointfloat) exponent
intpart ::= digit+
fraction ::= "." digit+
exponent ::= ("e" | "E") ["+" | "-"] digit+

Lexical analysis - 2.4.4 Integer literals

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::= nonzerodigit digit* | "0"+
nonzerodigit ::= "1"..."9"
digit ::= "0"..."9"
octinteger ::= "0" ("o" | "O") octdigit+
hexinteger ::= "0" ("x" | "X") hexdigit+
bininteger ::= "0" ("b" | "B") bindigit+
octdigit ::= "0"..."7"
hexdigit ::= digit | "a"..."f" | "A"..."F"
bindigit ::= "0" | "1"

Typescript indexed properties constraint checks working on raw types but not on object literals?

This is due to the way type compatibility works in TypeScript:

Type compatibility in TypeScript is based on structural subtyping. Structural typing is a way of relating types based solely on their members. This is in contrast with nominal typing.

So, this works:

interface Foo {}
let f: Foo = 3;

Because for each property on {}, there is a property with the same name and type on Number.

That means that this does not work:

interface Foo {}
let f: Foo = 3;
let b: number = f;

The compiler complains about let b: number = f;:

Type 'Foo' is not assignable to type 'number';

Again, from the documentation:

TypeScript’s structural type system was designed based on how JavaScript code is typically written. Because JavaScript widely uses anonymous objects like function expressions and object literals, it’s much more natural to represent the kinds of relationships found in JavaScript libraries with a structural type system instead of a nominal one.

Attribute access on int literals

Read carefully, it says

Whitespace is needed between two tokens only if their concatenation could otherwise be interpreted as a different token (e.g., ab is one token, but a b is two tokens).

1.__hash__() is tokenized as:

import io, tokenize
for token in tokenize.tokenize(io.BytesIO(b"1.__hash__()").read):
print(token.string)

#>>> utf-8
#>>> 1.
#>>> __hash__
#>>> (
#>>> )
#>>>

Python's lexer will choose a token which comprises the longest possible string that forms a legal token, when read from left to right; after parsing no two tokens should be able to be combined into a valid token. The logic is very similar to that in your other question.

The confusion seems to be not recognizing the tokenizing step as a completely distinct step. If the grammar allowed splitting up tokens solely to make the parser happy then surely you'd expect

_ or1.

to tokenize as

_
or
1.

but there is no such rule, so it tokenizes as

_
or1
.

different syntax for accessing method of literals

Reason for this to happen:

It's because 1. gets treated as a float:

>>> 1.
1.0
>>>

And obviously:

>>> 1.0__eq__
SyntaxError: invalid decimal literal
>>>

Would give an error.

It's because Python operates from left to right. So the dot would belong to 1 to make it a float.

Workaround for this other than parenthesis:

So the way to fix it would be to add a space between 1 and the dot ., like:

>>> 1 .__eq__
<method-wrapper '__eq__' of int object at 0x00007FFDF14B2730>
>>>

Reasoning for these workarounds to work:

The reason this works is because:

>>> 1 .
SyntaxError: invalid syntax
>>>

Gives an error, so it doesn't get treated as an integer.

It's the same case for (1).

As you can see:

>>> (1).
SyntaxError: invalid syntax
>>>

Documentation references:

As shown on this page of the documentation:

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::= nonzerodigit digit* | "0"+
nonzerodigit ::= "1"..."9"
digit ::= "0"..."9"
octinteger ::= "0" ("o" | "O") octdigit+
hexinteger ::= "0" ("x" | "X") hexdigit+
bininteger ::= "0" ("b" | "B") bindigit+
octdigit ::= "0"..."7"
hexdigit ::= digit | "a"..."f" | "A"..."F"
bindigit ::= "0" | "1"

The above is the integer literal definitions in Python.

How to get the value of a (TypeScript) template literal type programatically?

I found the solution. You cannot retrieve it from the return value of addTypeAlias. Instead, I had to do:

sourceFile.getTypeAliasOrThrow('MyTemplateLiteralType').getType().getLiteralValue()

Typescript Type 'string' is not assignable to type

Update

As mentioned in @Simon_Weaver's answer, since TypeScript version 3.4 it's possible to assert it to const:

let fruit = "Banana" as const;

Old answer

You'll need to cast it:

export type Fruit = "Orange" | "Apple" | "Banana";
let myString: string = "Banana";

let myFruit: Fruit = myString as Fruit;

Also notice that when using string literals you need to use only one |



Related Topics



Leave a reply



Submit