Skip to content

Function arguments allow unknown properties when passed as variables #31412

Closed

Description

TypeScript Version: 3.5.0-dev.20190502

Code

export interface ThemeVar {
  light: string;
  dark: string;
}

type Theme = keyof ThemeVar | { name: keyof ThemeVar };

function themed(
  styles: TemplateStringsArray,
  ...themedVars: ThemeVar[]
): any[] {
  const processedThemedVars = themedVars.map(
    themedVar => ({ theme }: { theme: Theme }) =>
      typeof theme === 'string'
        ? themedVar[theme]
        : themedVar[theme.name],
  );

  return styles.map((string, i) => [
    string,
    processedThemedVars[i] || '',
  ]);
}

const colors = {
  dark: '#FFF',
  light: '#000',
  shouldFail: 0
}

const test = themed`
  color: ${colors}; // VALID ???
`;

const test2 = themed`
  color: ${{
    dark: '#FFF',
    light: '#000',
    shouldFail: 0 // INVALID
    // Object literal may only specify known properties,
    // and 'shouldFail' does not exist in type 'ThemeVar'.
  }};
`;

function themedFn(themeVar: ThemeVar) {
  return themeVar;
}

themedFn(colors); // VALID ???

themedFn({
  dark: '#FFF',
  light: '#000',
  shouldFail: 0 // INVALID
  // Object literal may only specify known properties,
  // and 'shouldFail' does not exist in type 'ThemeVar'.
});

Expected behavior:

Object literals passed as variables should fail with unknown properties just as they do when passed directly.

Actual behavior:

Object literals passed as variables allow for unknown properties.

Playground Link: Playground Link

Related Issues: #29578

Activity

  1. RyanCavanaugh commented on May 15, 2019

    @RyanCavanaugh
    Member

    This is the intended behavior.

  2. poseidonCore commented on May 16, 2019

    @poseidonCore

    You may easily enforce type constraints for your inputs before they are consumed to catch this type of error:

    const colors:ThemeVar = {
      dark: '#FFF',
      light: '#000',
      shouldFail: 0 // Will show error.
    }
    
    /* ERROR
    Type '{ dark: string; light: string; shouldFail: number; }' is not assignable to type 'ThemeVar'.
      Object literal may only specify known properties, and 'shouldFail' does not exist in type 'ThemeVar'.
    (property) shouldFail: number
    */
  3. seanmcintyre commented on May 16, 2019

    @seanmcintyre
    Author

    poseidonCore This for a library, and while it's simple enough to do that internally, it's a bad developer experience to need to import ThemeVar and repeatedly specify it manually. The actual use case is a convenience utility within styled-components:

    const Text = styled.p<Props>`
      font: 300 1.5rem / 2rem ${fontFamily};
      letter-spacing: 0.01rem;
      margin-bottom: 1.5rem;
    
      ${themed`
        color: ${colors};
        ${antialias};
      `}
    `;
  4. fatcerberus commented on May 16, 2019

    @fatcerberus

    I understand the frustration but the excess properties must be allowed; it’s not just a convenience, but actually required in a structural typing system in order to be able to express subclasses at all.

    Excess is checked for in direct object literals because extra properties there are nearly always an error, but in general, if class D extends C with some extra stuff then disallowing excess properties across the board would prevent objects of type D from being assignable to C. Not very practical.

  5. fatcerberus commented on May 16, 2019

    @fatcerberus

    Hmm... though the more I think about it, I do wonder if the freshness checks could safely be extended... I might have an idea for a proposal. I’ll give it some thought.

  6. poseidonCore commented on May 16, 2019

    @poseidonCore

    siobhán (@seanmcintyre), if you are willing to export a class from a library and import it repeatedly elsewhere, then you should be willing to do the same for an interface/type used in that class. This particular practice and developer experience is common throughout most strictly typed languages.

    I feel your frustration with this issue because I used to feel the same, but applying the simple pattern that I described is the only real way around this. As Ryan Cavanaugh (@RyanCavanaugh) has pointed out, this is a language feature and so we have to work with this.

  7. typescript-bot commented on May 18, 2019

    @typescript-bot
    Contributor

    This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  8. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions