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

Use SingletonStrT, SingletonNumT, SingletonBoolT in case of const literal declarations #7607

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions src/typing/statement.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2811,7 +2811,10 @@ and variable cx kind ?if_uninitialized id init = Ast.Statement.(
| (_, Ast.Pattern.Identifier _), None, None ->
None, None
| _, Some expr, _ ->
let (_, t), _ as init_ast = expression cx expr in
let (_, t), _ as init_ast = match kind with
| VariableDeclaration.Const -> expression ~is_const:true cx expr
| _ -> expression cx expr
in
let r = mk_expression_reason expr in
Some (t, r), Some init_ast
| (ploc, _), None, Some f ->
Expand Down Expand Up @@ -2921,8 +2924,8 @@ and expression_or_spread_list cx undef_loc = Ast.Expression.(
)

(* can raise Abnormal.(Exn (Stmt _, _)) *)
and expression ?(is_cond=false) cx (loc, e) =
expression_ ~is_cond cx loc e
and expression ?(is_cond=false) ?(is_const=false) cx (loc, e) =
expression_ ~is_cond ~is_const cx loc e

and this_ cx loc = Ast.Expression.(
match Refinement.get cx (loc, This) loc with
Expand All @@ -2933,12 +2936,12 @@ and this_ cx loc = Ast.Expression.(
and super_ cx loc =
Env.var_ref cx (internal_name "super") loc

and expression_ ~is_cond cx loc e : (ALoc.t, ALoc.t * Type.t) Ast.Expression.t =
and expression_ ~is_cond ~is_const cx loc e : (ALoc.t, ALoc.t * Type.t) Ast.Expression.t =
let make_trust = Context.trust_constructor cx in
let ex = (loc, e) in Ast.Expression.(match e with

| Ast.Expression.Literal lit ->
(loc, literal cx loc lit), Ast.Expression.Literal lit
(loc, literal ~is_const cx loc lit), Ast.Expression.Literal lit

(* Treat the identifier `undefined` as an annotation for error reporting
* purposes. Like we do with other literals. Otherwise we end up pointing to
Expand Down Expand Up @@ -4299,7 +4302,7 @@ and identifier cx { Ast.Identifier.name; comments= _ } loc =
t

(* traverse a literal expression, return result type *)
and literal cx loc lit =
and literal ?(is_const=false) cx loc lit =
let make_trust = Context.trust_constructor cx in
Ast.Literal.(match lit.Ast.Literal.value with
| String s -> begin
Expand All @@ -4317,17 +4320,36 @@ and literal cx loc lit =
then Literal (None, s), RString
else AnyLiteral, RLongStringLit (max_literal_length)
in
DefT (annot_reason (mk_reason r_desc loc), make_trust (), StrT lit)
match (lit, r_desc) with
| (Literal (_, s), _) when is_const ->
let reason = mk_reason (RStringLit s) loc in
Tvar.mk_where cx reason (fun tvar ->
Flow.flow_t cx (DefT (annot_reason reason, make_trust (), SingletonStrT s), tvar)
)
| (_, r_desc) ->
DefT (annot_reason (mk_reason r_desc loc), make_trust (), StrT lit)
end

| Boolean b ->
DefT (annot_reason (mk_reason RBoolean loc), make_trust (), BoolT (Some b))
if is_const then
let reason = mk_reason (RBooleanLit b) loc in
Tvar.mk_where cx reason (fun tvar ->
Flow.flow_t cx (DefT (annot_reason reason, make_trust (), SingletonBoolT b), tvar)
)
else
DefT (annot_reason (mk_reason RBoolean loc), make_trust (), BoolT (Some b))

| Null ->
NullT.at loc |> with_trust make_trust

| Number f ->
DefT (annot_reason (mk_reason RNumber loc), make_trust (), NumT (Literal (None, (f, lit.raw))))
if is_const then
let reason = mk_reason (RNumberLit lit.raw) loc in
Tvar.mk_where cx reason (fun tvar ->
Flow.flow_t cx (DefT (annot_reason reason, make_trust (), SingletonNumT (f, lit.raw)), tvar)
)
else
DefT (annot_reason (mk_reason RNumber loc), make_trust (), NumT (Literal (None, (f, lit.raw))))

| BigInt _ ->
let reason = annot_reason (mk_reason (RBigIntLit lit.raw) loc) in
Expand Down
22 changes: 17 additions & 5 deletions tests/singleton/boolean.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ function veryOptimistic(isThisAwesome: true): boolean {
return isThisAwesome;
}

var x : boolean = veryOptimistic(true);
var y : boolean = veryOptimistic(false); // error
var x: boolean = veryOptimistic(true);
var y: boolean = veryOptimistic(false); // error

function veryPessimistic(isThisAwesome: true): boolean {
return !isThisAwesome; // test bool conversion
}

var x : boolean = veryPessimistic(true);
var y : boolean = veryPessimistic(false); // error
var x: boolean = veryPessimistic(true);
var y: boolean = veryPessimistic(false); // error

type MyOwnBooleanLOL = true | false
type MyOwnBooleanLOL = true | false;

function bar(x: MyOwnBooleanLOL): false {
if (x) {
Expand All @@ -35,3 +35,15 @@ function alwaysFalsy(x: boolean): false {
return x;
}
}

function constLiteral() {
const foo = true;
const foo_check: typeof foo = false;
const mutable_foo = {
foo,
method() {
mutable_foo.foo = 1;
}
};
(mutable_foo.foo: boolean); // error
}
12 changes: 12 additions & 0 deletions tests/singleton/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ bar(3); // error
type ComparatorResult = -1 | 0 | 1
function sort(fn: (x: any, y: any) => ComparatorResult) {}
sort((x, y) => -1);

function constLiteral() {
const foo = 1;
const foo_check: typeof foo = 2;
const mutable_foo = {
foo,
method() {
mutable_foo.foo = 'str';
}
};
(mutable_foo.foo: number); // error
}
112 changes: 103 additions & 9 deletions tests/singleton/singleton.exp
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
Error -------------------------------------------------------------------------------------------------- boolean.js:8:34
Error -------------------------------------------------------------------------------------------------- boolean.js:8:33

Cannot call `veryOptimistic` with `false` bound to `isThisAwesome` because boolean [1] is incompatible with boolean
literal `true` [2].

boolean.js:8:34
8| var y : boolean = veryOptimistic(false); // error
^^^^^ [1]
boolean.js:8:33
8| var y: boolean = veryOptimistic(false); // error
^^^^^ [1]

References:
boolean.js:3:40
3| function veryOptimistic(isThisAwesome: true): boolean {
^^^^ [2]


Error ------------------------------------------------------------------------------------------------- boolean.js:15:35
Error ------------------------------------------------------------------------------------------------- boolean.js:15:34

Cannot call `veryPessimistic` with `false` bound to `isThisAwesome` because boolean [1] is incompatible with boolean
literal `true` [2].

boolean.js:15:35
15| var y : boolean = veryPessimistic(false); // error
^^^^^ [1]
boolean.js:15:34
15| var y: boolean = veryPessimistic(false); // error
^^^^^ [1]

References:
boolean.js:10:41
Expand Down Expand Up @@ -73,6 +73,38 @@ References:
^^^^^^^^^^^^^^^ [2]


Error ------------------------------------------------------------------------------------------------- boolean.js:41:33

Cannot assign `false` to `foo_check` because boolean literal `false` [1] is incompatible with boolean literal
`true` [2].

boolean.js:41:33
41| const foo_check: typeof foo = false;
^^^^^ [1]

References:
boolean.js:41:20
41| const foo_check: typeof foo = false;
^^^^^^^^^^ [2]


Error -------------------------------------------------------------------------------------------------- boolean.js:48:4

Cannot cast `mutable_foo.foo` to boolean because number [1] is incompatible with boolean [2].

boolean.js:48:4
48| (mutable_foo.foo: boolean); // error
^^^^^^^^^^^^^^^

References:
boolean.js:45:25
45| mutable_foo.foo = 1;
^ [1]
boolean.js:48:21
48| (mutable_foo.foo: boolean); // error
^^^^^^^ [2]


Error --------------------------------------------------------------------------------------------------- number.js:8:12

Cannot call `highlander` with `2` bound to `howMany` because number [1] is incompatible with number literal `1` [2].
Expand Down Expand Up @@ -101,5 +133,67 @@ References:
^^^ [2]


Error -------------------------------------------------------------------------------------------------- number.js:27:33

Cannot assign `2` to `foo_check` because number literal `2` [1] is incompatible with number literal `1` [2].

number.js:27:33
27| const foo_check: typeof foo = 2;
^ [1]

References:
number.js:27:20
27| const foo_check: typeof foo = 2;
^^^^^^^^^^ [2]


Error --------------------------------------------------------------------------------------------------- number.js:34:4

Cannot cast `mutable_foo.foo` to number because string [1] is incompatible with number [2].

number.js:34:4
34| (mutable_foo.foo: number); // error
^^^^^^^^^^^^^^^

References:
number.js:31:25
31| mutable_foo.foo = 'str';
^^^^^ [1]
number.js:34:21
34| (mutable_foo.foo: number); // error
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------------- string.js:11:33

Cannot assign `'bar'` to `foo_check` because string literal `bar` [1] is incompatible with string literal `str` [2].

string.js:11:33
11| const foo_check: typeof foo = 'bar';
^^^^^ [1]

References:
string.js:11:20
11| const foo_check: typeof foo = 'bar';
^^^^^^^^^^ [2]


Error --------------------------------------------------------------------------------------------------- string.js:18:4

Cannot cast `mutable_foo.foo` to string because number [1] is incompatible with string [2].

string.js:18:4
18| (mutable_foo.foo: string); // error
^^^^^^^^^^^^^^^

References:
string.js:15:25
15| mutable_foo.foo = 2;
^ [1]
string.js:18:21
18| (mutable_foo.foo: string); // error
^^^^^^ [2]



Found 7 errors
Found 13 errors
12 changes: 12 additions & 0 deletions tests/singleton/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ type NoSpaces = "foobar"

type HasSpaces = "foo bar"
("foo bar": HasSpaces);

function constLiteral() {
const foo = 'str';
const foo_check: typeof foo = 'bar';
const mutable_foo = {
foo,
method() {
mutable_foo.foo = 2;
}
};
(mutable_foo.foo: string); // error
}
10 changes: 5 additions & 5 deletions tests/type-at-pos_expression/type-at-pos_expression.exp
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ jsx.js:18:24 = {
"end":24
}
literals.js:6:7 = {
"type":"string",
"type":"\"A\"",
"reasons":[],
"loc":{
"source":"literals.js",
Expand All @@ -284,7 +284,7 @@ literals.js:6:7 = {
"end":7
}
literals.js:7:7 = {
"type":"number",
"type":"1",
"reasons":[],
"loc":{
"source":"literals.js",
Expand All @@ -299,7 +299,7 @@ literals.js:7:7 = {
"end":7
}
literals.js:8:7 = {
"type":"number",
"type":"1.01",
"reasons":[],
"loc":{
"source":"literals.js",
Expand All @@ -314,7 +314,7 @@ literals.js:8:7 = {
"end":7
}
literals.js:9:7 = {
"type":"boolean",
"type":"true",
"reasons":[],
"loc":{
"source":"literals.js",
Expand All @@ -329,7 +329,7 @@ literals.js:9:7 = {
"end":7
}
literals.js:10:7 = {
"type":"number | string | boolean",
"type":"1 | 1.01 | \"A\" | true",
"reasons":[],
"loc":{
"source":"literals.js",
Expand Down