-1

I have the following TypeScript code in VS Code:

const f1 = ():string => { return "abc"; }

const d:any = {}
d.f2 = ():string => { return "abc"; }

Putting the mouse over f1, it says const f1: () => string.

Putting the mouse over f2, it says any.

Shouldn't f2 also be a function that returns a string?

Old Geezer
  • 14,854
  • 31
  • 111
  • 198
  • 2
    In *neither* case is the return type of the function a function; the return type of `f1` is a *string*. But the type of *any* prop on something typed `any` will also be `any` - that's what `any` *means*. – jonrsharpe Dec 22 '20 at 15:50
  • 2
    The title should be edited since we're not taking about *return* types. Maybe "Compiler forgets type after assignment?" or something? The issue here is a combination of "assignments to variables annotated with a non-union type do not narrow the type of the variable" and "`any` is infectious" – jcalz Dec 22 '20 at 15:54

2 Answers2

1

No, because the object holding it has type any, so Typescript cannot infer it will always be a function. The intention of any is not throwing type checking errors. If it infers the function type in this case, it would also block you from assigning a value with a different type, which might be undesired.

When a value is of type any, you can access any properties of it (which will in turn be of type any)

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any

AqueleHugo
  • 168
  • 6
1

If you have a non-union type Foo like this:

interface Foo {
  a: string;
}

and if you annotate a variable as that type:

let foo: Foo = { a: "hello" };

then any subsequent assignments or property changes will not affect the apparent type of that variable. For example:

let bar = { a: "hello", b: "goodbye" };
bar.b; // okay
foo = bar; // okay, bar is assignable to Foo
foo.b // <-- error! Property 'b' does not exist on type 'Foo'

Here we have a value, bar, known to have a b property. We can assign bar to foo because bar is a valid Foo (it has an a property). But once we do that assignment, the compiler has not changed the apparent type of foo. It's still just Foo, and therefore we can no longer read a b property from it.

Note: for union types, it's more complicated because control flow analysis (microsoft/TypeScript#8010) causes some assignments to apparently narrow the variable's type to some subset of the union members. But according to a comment in microsoft/TypeScript#8513, they haven't done this for non-union types.


You annotated d as type any, which is a non-union type. After that, any properties you assign will be immediately forgotten.

Furthermore, the any type is an escape hatch from the type system (see this answer). It is infectious and propagates outward to a lot of other types it touches: any | Foo is any; any & Foo is any; any["somePropKey"] is any. The last one, where you look up a property and get any is what happened to d.f2. It's just any.


If you don't want this to happen, you should stay away from any and instead use stronger types, preferably more immutable objects, and let the compiler infer instead of manually annotate where possible:

const d2 = {
  f2: () => { return "abc" }
}
d2.f2().toUpperCase(); // okay

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360