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

Add heuristic for extracting irreducible null and undefined types from intersections of unions #33150

Merged
merged 1 commit into from Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/compiler/checker.ts
Expand Up @@ -9996,6 +9996,16 @@ namespace ts {
return true;
}

function extractIrreducible(types: Type[], flag: TypeFlags) {
if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add Null and Undefined to TypeFlags.IncludesMask to avoid the second check?

actually, would it be possible to add a special TypeFlags.EveryUnion that gets calculated in addTypesToIntersection and then added to includes? This uses up another TypeFlags, but is faster than trying to return two things. So..maybe not worth it.

I do notice that the two calls to extractIrreducible might iterate typeSet twice whenever it contains all-unions, but doesn't have any null or undefineds.

Copy link
Member Author

@weswigham weswigham Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add Null and Undefined to TypeFlags.IncludesMask to avoid the second check?

Nope, because we're looking for members-of-members that are undefined-or-null. We'd need 3 (or two) brand new flags (null and undefined are already used to track weather the intersection itself contains them). One for "every member is a union", one for "every member contains undefined", and one for "every member contains null". We only have two unused type flags left (all currently assigned type flags have a meaning in intersection construction), sooo.... Yeah.

This already bails early in every common scenario, the worst case where it's not needed is soemthing like a 52 element union where the first 51 contain undefined, but the 52nd contains null, which is irreducible and, notably, will trigger the "excessively large union" check in the else clause once the filtering fails, and cease to do any more work. So the "worst case" is either the case where it needs to occur and all the work needs to be done, or where it's very close to that but not, and will likely be converted to any, anyway.

for (let i = 0; i < types.length; i++) {
types[i] = filterType(types[i], t => !(t.flags & flag));
}
return true;
}
return false;
}

// If the given list of types contains more than one union of primitive types, replace the
// first with a union containing an intersection of those primitive types, then remove the
// other unions and return true. Otherwise, do nothing and return false.
Expand Down Expand Up @@ -10114,6 +10124,12 @@ namespace ts {
// reduced we'll never reduce again, so this occurs at most once.
result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
}
else if (extractIrreducible(typeSet, TypeFlags.Undefined)) {
result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we run both, for large intersections of the form x | null | undefined & ...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do - note the recursive getIntersectionType call (which is needed to recalculate includes for the now filtered type set)

}
else if (extractIrreducible(typeSet, TypeFlags.Null)) {
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
}
else {
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
Expand Down
85 changes: 85 additions & 0 deletions tests/baselines/reference/partialOfLargeAPIIsAbleToBeWorkedWith.js
@@ -0,0 +1,85 @@
//// [partialOfLargeAPIIsAbleToBeWorkedWith.ts]
interface MyAPI {
0: (x: 0) => string;
1: (x: 1) => string;
2: (x: 2) => string;
3: (x: 3) => string;
4: (x: 4) => string;
5: (x: 5) => string;
6: (x: 6) => string;
7: (x: 7) => string;
8: (x: 8) => string;
9: (x: 9) => string;
10: (x: 10) => string;
11: (x: 11) => string;
12: (x: 12) => string;
13: (x: 13) => string;
14: (x: 14) => string;
15: (x: 15) => string;
16: (x: 16) => string;
17: (x: 17) => string;
18: (x: 18) => string;
19: (x: 19) => string;
20: (x: 20) => string;
21: (x: 21) => string;
22: (x: 22) => string;
23: (x: 23) => string;
24: (x: 24) => string;
25: (x: 25) => string;
26: (x: 26) => string;
27: (x: 27) => string;
28: (x: 28) => string;
29: (x: 29) => string;
30: (x: 30) => string;
31: (x: 31) => string;
32: (x: 32) => string;
33: (x: 33) => string;
34: (x: 34) => string;
35: (x: 35) => string;
36: (x: 36) => string;
37: (x: 37) => string;
38: (x: 38) => string;
39: (x: 39) => string;
40: (x: 40) => string;
41: (x: 41) => string;
42: (x: 42) => string;
43: (x: 43) => string;
44: (x: 44) => string;
45: (x: 45) => string;
46: (x: 46) => string;
47: (x: 47) => string;
48: (x: 48) => string;
49: (x: 49) => string;
50: (x: 50) => string;
51: (x: 51) => string;
}

const obj: Partial<MyAPI> = {};

declare var keys: (keyof MyAPI)[];

for (const k of keys) {
obj[k] = () => "12"; // shouldn't cause a complexity error
}

type PartialNull<T> = {[K in keyof T]?: T[K] | null};

const obj2: PartialNull<MyAPI> = {};

for (const k of keys) {
obj2[k] = () => "12"; // shouldn't cause a complexity error
}


//// [partialOfLargeAPIIsAbleToBeWorkedWith.js]
"use strict";
var obj = {};
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
var k = keys_1[_i];
obj[k] = function () { return "12"; }; // shouldn't cause a complexity error
}
var obj2 = {};
for (var _a = 0, keys_2 = keys; _a < keys_2.length; _a++) {
var k = keys_2[_a];
obj2[k] = function () { return "12"; }; // shouldn't cause a complexity error
}
@@ -0,0 +1,253 @@
=== tests/cases/compiler/partialOfLargeAPIIsAbleToBeWorkedWith.ts ===
interface MyAPI {
>MyAPI : Symbol(MyAPI, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 0, 0))

0: (x: 0) => string;
>0 : Symbol(MyAPI[0], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 0, 17))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 1, 8))

1: (x: 1) => string;
>1 : Symbol(MyAPI[1], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 1, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 2, 8))

2: (x: 2) => string;
>2 : Symbol(MyAPI[2], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 2, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 3, 8))

3: (x: 3) => string;
>3 : Symbol(MyAPI[3], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 3, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 4, 8))

4: (x: 4) => string;
>4 : Symbol(MyAPI[4], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 4, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 5, 8))

5: (x: 5) => string;
>5 : Symbol(MyAPI[5], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 5, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 6, 8))

6: (x: 6) => string;
>6 : Symbol(MyAPI[6], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 6, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 7, 8))

7: (x: 7) => string;
>7 : Symbol(MyAPI[7], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 7, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 8, 8))

8: (x: 8) => string;
>8 : Symbol(MyAPI[8], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 8, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 9, 8))

9: (x: 9) => string;
>9 : Symbol(MyAPI[9], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 9, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 10, 8))

10: (x: 10) => string;
>10 : Symbol(MyAPI[10], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 10, 24))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 11, 9))

11: (x: 11) => string;
>11 : Symbol(MyAPI[11], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 11, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 12, 9))

12: (x: 12) => string;
>12 : Symbol(MyAPI[12], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 12, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 13, 9))

13: (x: 13) => string;
>13 : Symbol(MyAPI[13], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 13, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 14, 9))

14: (x: 14) => string;
>14 : Symbol(MyAPI[14], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 14, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 15, 9))

15: (x: 15) => string;
>15 : Symbol(MyAPI[15], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 15, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 16, 9))

16: (x: 16) => string;
>16 : Symbol(MyAPI[16], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 16, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 17, 9))

17: (x: 17) => string;
>17 : Symbol(MyAPI[17], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 17, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 18, 9))

18: (x: 18) => string;
>18 : Symbol(MyAPI[18], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 18, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 19, 9))

19: (x: 19) => string;
>19 : Symbol(MyAPI[19], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 19, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 20, 9))

20: (x: 20) => string;
>20 : Symbol(MyAPI[20], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 20, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 21, 9))

21: (x: 21) => string;
>21 : Symbol(MyAPI[21], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 21, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 22, 9))

22: (x: 22) => string;
>22 : Symbol(MyAPI[22], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 22, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 23, 9))

23: (x: 23) => string;
>23 : Symbol(MyAPI[23], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 23, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 24, 9))

24: (x: 24) => string;
>24 : Symbol(MyAPI[24], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 24, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 25, 9))

25: (x: 25) => string;
>25 : Symbol(MyAPI[25], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 25, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 26, 9))

26: (x: 26) => string;
>26 : Symbol(MyAPI[26], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 26, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 27, 9))

27: (x: 27) => string;
>27 : Symbol(MyAPI[27], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 27, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 28, 9))

28: (x: 28) => string;
>28 : Symbol(MyAPI[28], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 28, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 29, 9))

29: (x: 29) => string;
>29 : Symbol(MyAPI[29], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 29, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 30, 9))

30: (x: 30) => string;
>30 : Symbol(MyAPI[30], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 30, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 31, 9))

31: (x: 31) => string;
>31 : Symbol(MyAPI[31], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 31, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 32, 9))

32: (x: 32) => string;
>32 : Symbol(MyAPI[32], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 32, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 33, 9))

33: (x: 33) => string;
>33 : Symbol(MyAPI[33], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 33, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 34, 9))

34: (x: 34) => string;
>34 : Symbol(MyAPI[34], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 34, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 35, 9))

35: (x: 35) => string;
>35 : Symbol(MyAPI[35], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 35, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 36, 9))

36: (x: 36) => string;
>36 : Symbol(MyAPI[36], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 36, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 37, 9))

37: (x: 37) => string;
>37 : Symbol(MyAPI[37], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 37, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 38, 9))

38: (x: 38) => string;
>38 : Symbol(MyAPI[38], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 38, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 39, 9))

39: (x: 39) => string;
>39 : Symbol(MyAPI[39], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 39, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 40, 9))

40: (x: 40) => string;
>40 : Symbol(MyAPI[40], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 40, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 41, 9))

41: (x: 41) => string;
>41 : Symbol(MyAPI[41], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 41, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 42, 9))

42: (x: 42) => string;
>42 : Symbol(MyAPI[42], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 42, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 43, 9))

43: (x: 43) => string;
>43 : Symbol(MyAPI[43], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 43, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 44, 9))

44: (x: 44) => string;
>44 : Symbol(MyAPI[44], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 44, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 45, 9))

45: (x: 45) => string;
>45 : Symbol(MyAPI[45], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 45, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 46, 9))

46: (x: 46) => string;
>46 : Symbol(MyAPI[46], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 46, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 47, 9))

47: (x: 47) => string;
>47 : Symbol(MyAPI[47], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 47, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 48, 9))

48: (x: 48) => string;
>48 : Symbol(MyAPI[48], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 48, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 49, 9))

49: (x: 49) => string;
>49 : Symbol(MyAPI[49], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 49, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 50, 9))

50: (x: 50) => string;
>50 : Symbol(MyAPI[50], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 50, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 51, 9))

51: (x: 51) => string;
>51 : Symbol(MyAPI[51], Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 51, 26))
>x : Symbol(x, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 52, 9))
}

const obj: Partial<MyAPI> = {};
>obj : Symbol(obj, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 55, 5))
>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --))
>MyAPI : Symbol(MyAPI, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 0, 0))

declare var keys: (keyof MyAPI)[];
>keys : Symbol(keys, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 57, 11))
>MyAPI : Symbol(MyAPI, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 0, 0))

for (const k of keys) {
>k : Symbol(k, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 59, 10))
>keys : Symbol(keys, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 57, 11))

obj[k] = () => "12"; // shouldn't cause a complexity error
>obj : Symbol(obj, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 55, 5))
>k : Symbol(k, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 59, 10))
}

type PartialNull<T> = {[K in keyof T]?: T[K] | null};
>PartialNull : Symbol(PartialNull, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 61, 1))
>T : Symbol(T, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 63, 17))
>K : Symbol(K, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 63, 24))
>T : Symbol(T, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 63, 17))
>T : Symbol(T, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 63, 17))
>K : Symbol(K, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 63, 24))

const obj2: PartialNull<MyAPI> = {};
>obj2 : Symbol(obj2, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 65, 5))
>PartialNull : Symbol(PartialNull, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 61, 1))
>MyAPI : Symbol(MyAPI, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 0, 0))

for (const k of keys) {
>k : Symbol(k, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 67, 10))
>keys : Symbol(keys, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 57, 11))

obj2[k] = () => "12"; // shouldn't cause a complexity error
>obj2 : Symbol(obj2, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 65, 5))
>k : Symbol(k, Decl(partialOfLargeAPIIsAbleToBeWorkedWith.ts, 67, 10))
}