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

Partials accept functions #17003

Closed
luke-john opened this issue Jul 7, 2017 · 6 comments
Closed

Partials accept functions #17003

luke-john opened this issue Jul 7, 2017 · 6 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@luke-john
Copy link

luke-john commented Jul 7, 2017

TypeScript Version: 2.4.0 / nightly (2.5.0-dev.201xxxxx)

2.4.1 and 2.5.0-dev.20170629 and 2.5.0-dev.20170707

Code

interface Foo {
    cat: string
    bar: number
}

type Bar = Partial<Foo>

const notBar: Bar = () => { } // passes on 2.4.1 and 2.5.0-dev.20170629 and 2.5.0-dev.20170707

Expected behavior:

Partials should not accept functions

Actual behavior:

Partials accept functions

typescript-playground-example

Related Issues:

13132 has been marked as a duplicate of 7485 which has been closed

7485 is meant was meant to be closed by 16047 which has merged.

Edit: updated after testing on latest nightly and simplified example code

@luke-john luke-john changed the title Bug: Partials accept functions Partials accept functions Jul 7, 2017
@kitsonk
Copy link
Contributor

kitsonk commented Jul 7, 2017

Addressed in TypeScript FAQs.

@luke-john
Copy link
Author

luke-john commented Jul 7, 2017

Apologies but it's not clear to me why a function is assignable to a partial interface based on that.

My issue is not to do with the parameters on the function, but that a function is assignable to a partial interface

@kitsonk
Copy link
Contributor

kitsonk commented Jul 7, 2017

Ah, I see the challenge...

From a type perspective, functions are a type of object, which can have properties. You are assigning a function to an object with no required properties. Therefore, a function with no properties is assignable to an interface with all optional properties.

Why all the cases you have in your playground example was because TypeScript did not detect weak types properly, and as of 2.4 it does. That means that strings and numbers are no longer assignable but these weak types.

I tried to use object & Partial<T> as a type to avoid functions being assignable, but I am afraid functions are still considered subtypes of objects by the type system. But I might be missing something...

@luke-john
Copy link
Author

luke-john commented Jul 8, 2017

Edit: Upon trying the below unfortunately it does not work due to an issue with unions

interface SinglePropA { a: 'test-a' }
interface SinglePropB { b: 'test-b' }

type SubsetAnimal =
    | SinglePropA
    | SinglePropB

const testAandB: SubsetAnimal = {
    a: 'test-a',
    b: 'test-bs'
} // passes on 2.4.1 and 2.5.0-dev.20170629 

So after sleeping on it, I think I can restructure my use of Partial to achieve what I want with the following.

type ValueOne = { valueOne: 'value-one' }
type ValueTwo = { valueTwo: 'value-two' }

type SingleValue =
    | ValueOne
    | ValueTwo

type MultiValue =
    & Partial<
        & ValueOne
        & ValueTwo
    >
    & SingleValue

type Value = SingleValue | MultiValue

type Method = () => Value

type Argument = Method | Value

type Library = (argument: Argument) => void

const library: Library = (props) => {}

library({
})

library(() => ({
}))

typescript-playground-example

Cheers for looking into it

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jul 9, 2017
@luke-john
Copy link
Author

luke-john commented Jul 10, 2017

Adding an index signature to the interface you pass to the Partial prevents it accepting functions.

typescript playground example

interface Foo {
    cat: string
    bar: number

    [key: string]: string | number | undefined
}

type Bar<Baz> =
    | Partial<Baz>
    | (
        () => Partial<Baz>
    )

const notBar: Bar<Foo> = () => { } // fails on 2.4.1 and 2.5.0-dev.20170629 

const alsoNotBar: Bar<Foo> = 'test' // fails on 2.4.1

const alsoAlsoNotBar: Bar<Foo> = false // fails on 2.4.1

edit: note, this does kill autocomplete though :/

@luke-john
Copy link
Author

just updating for anyone who comes across this via google.

on 2.4.1 the following works and retains autocomplete.

interface StylesComplete {
  propA: 'letter a' | 'a',
  propB: 'letter b' | 'b',
}

// A Partial on an interface will normally accept arbitrary functions
// due to partials accepting empty objects which validate to anything in
// typescript
const doesNotFail: Partial<StylesComplete> = () => { }


interface StylesLossy {
  [key: string]: string | undefined
}

type Styles =
  & StylesComplete
  & StylesLossy

// intersection of
// * an interface containing random props
// * index signature interface
// allows  you to do a partial which will successfully fail on arbitrary
// functions.

// successfully fails
const doesFail: Partial<Styles> = () => { }

typescript-playground-example

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants