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

Object literal expanded to the wrong union type arm #22019

Closed
frankpf opened this issue Feb 18, 2018 · 3 comments
Closed

Object literal expanded to the wrong union type arm #22019

frankpf opened this issue Feb 18, 2018 · 3 comments

Comments

@frankpf
Copy link

frankpf commented Feb 18, 2018

TypeScript Version: 2.8.0-dev.20180217

Search Terms: "is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes", "Property '[Symbol.unscopables]' is missing in type"

Code

bug.tsx:

// Test this by running tsc --jsx
import * as React from 'react';
import { View } from 'react-native'

const screen = {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
}

export function Login() {
  return(
    <View style={ screen }>
    </View>
  )
}

I also created a repository with the correct dependencies to make debugging this easier:
https://github.com/frankpf/tsc-react-native-bug

Expected behavior:
No error.

Actual behavior:
On the <View style={ screen }>, I get this error:

src/bug.tsx(12,11): error TS2322: Type '{ children: never[]; style: { flex: number; justifyContent: string; alignItems: string; }; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<ViewProperties, ComponentState>> & Reado...'.
  Type '{ children: never[]; style: { flex: number; justifyContent: string; alignItems: string; }; }' is not assignable to type 'Readonly<ViewProperties>'.
    Types of property 'style' are incompatible.
      Type '{ flex: number; justifyContent: string; alignItems: string; }' is not assignable to type 'StyleProp<ViewStyle>'.
        Type '{ flex: number; justifyContent: string; alignItems: string; }' is not assignable to type 'RecursiveArray<false | ViewStyle | RegisteredStyle<ViewStyle> | null | undefined>'.
          Property '[Symbol.unscopables]' is missing in type '{ flex: number; justifyContent: string; alignItems: string; }'.

One super weird thing is that if I just pass the object literal, I get no error. So
<View style={ screen }>
errors, but this
<View style= { flex: 1, justifyContent: 'center', alignItems: 'center' }}>
does not.

If I remove the justifyContent and alignItems properties from the screen variable:

const screen = {
    flex: 1
}

tsc also doesn't complain.

Related Issues: #15463 and #15419 may be slightly related

@frankpf
Copy link
Author

frankpf commented Feb 18, 2018

I reduced this issue a lot.

test.ts:

import { StyleProp, ViewStyle, RecursiveArray, RegisteredStyle } from 'react-native'

const screen: ViewStyle = {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
}

let x: StyleProp<ViewStyle> = screen

Here are the definitions of StyleProp<T> and RecursiveArray<T>, taken from @types/react-native:

interface RecursiveArray<T> extends Array<T | RecursiveArray<T>> {}
export type StyleProp<T> = T | RegisteredStyle<T> | RecursiveArray<T | RegisteredStyle<T> | Falsy> | Falsy```;

test.ts compiles with no error, because screen is a valid ViewStyle.

However, if I simply remove the type hint from screen:

import { StyleProp, ViewStyle } from 'react-native'

const screen = {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
}

let x: StyleProp<ViewStyle> = screen

then tsc outputs the following on the let x assignment:

src/bug.tsx(9,5): error TS2322: Type '{ flex: number; justifyContent: string; alignItems: string; }' is not assignable to type 'StyleProp<ViewStyle>'.
  Type '{ flex: number; justifyContent: string; alignItems: string; }' is not assignable to type 'RecursiveArray<false | ViewStyle | RegisteredStyle<ViewStyle> | null | undefined>'.
    Property '[Symbol.unscopables]' is missing in type '{ flex: number; justifyContent: string; alignItems: string; }'.

To me, it seems that the main issue is that tsc is wrongly expanding screen to the RecursiveArray<T | ...> part of the StyleProp union instead of expanding it to just T.

@frankpf frankpf changed the title JSX: error when passing variable reference to props instead of the value Object literal expanded to the wrong union type arm Feb 18, 2018
@frankpf frankpf closed this as completed Feb 22, 2018
@Rovanion
Copy link

What resolved this issue?

@frankpf
Copy link
Author

frankpf commented Feb 26, 2018

@Rovanion Sorry, I should've explained the solution before closing the issue.

TLDR:

Use React Native's StyleSheet.create:

import { StyleSheet } from 'react-native'
const screen = StyleSheet.create({
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
})

Long answer:

const x = 'literal'

const y = {
   x: 'literal'
}

If you check the types, x is of type 'literal' but y.x is a string. This makes sense because y.x is mutable and x is not.

If you assign y to an interface expecting a literal string type, you get an error:

interface A {
   x: 'literal'
}
const y = {
   x: 'literal'
}

let a: A = y <-- tsc error: "Type string is not assignable to type literal"

OK, this is a bit unexpected but it's understandable. The problem happens when you use a union type:

interface A {
   x: 'literal'
}
const y = {
   x: 'literal'
}

type B = A | number[]

let a: B = y <-- tsc error: "Property 'length' is missing in type { x: string }"

The React Native bug in the original issue is an instance of this problem: ViewStyle contains a literal string type:

interface ViewStyle {
...
justifyContent: 'center' | 'flex-end' | ...
...
}

but the justifiyContent passed to it was of type string.

This is especially problematic for big libraries, as you get very long, non-intuitive error messages (you can see an example in the OP).

It seems that this only happens for some union types.

type B = A | number, for instance, shows the "correct" error message. I thought this only happened when arrays were in the union types, but interface Z { z: 'abc' }; type B = A | Z also errors with Property "z" is missing in { x: string }.

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants