Skip to content

Proposal private-fields-in-in is stage-3 #42574

Closed

Description

@Kingwl

Suggestion

https://github.com/tc39/proposal-private-fields-in-in is stage 3 now.

🔍 Search Terms

esnext. private field. class

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

📃 Motivating Example

class C {
  #brand = 42;

  static isC(obj: C) {
    return #brand in obj; // should work
  }
}

💻 Use Cases

Activity

  1. acutmore commented on Feb 10, 2021

    @acutmore
    Contributor

    Good news! A few of us at Bloomberg (@bloomberg) have started discussing working on this as a TS contribution

    cc: Titian Cernicova-Dragomir (@dragomirtitian) Rob Palmer (@robpalme)

  2. acutmore commented on Feb 20, 2021

    @acutmore
    Contributor

    Work has now started on this and is going well (basic parsing with simple errors and type checking).

    • Work merged to bloomberg/TypeScript
    • Add new 'PrivateIdentifierInInExpression' expression
      • 'prop' in obj is a BinaryExpression, but this isn't because syntactically the left side can only be a PrivateIdentifier
      • suggestions for alternative names instead of 'PrivateIdentifierInInExpression' ?
    • Error when the PrivateIdentifier can't be looked-up symbolically
      • outside of a class
      • no containing class with matching private field
    • Expression returns boolean
    • RHS must be object or any (and not null in strictNullChecks)
      • i.e. same rules as 'prop' in obj
    • Helpful error messages
    • Use in flowNodes and type narrowing
    • language server hints
      • suggest completion for #${privateFields} in on typing #
    • emit ESNext
    • emit down-level
    • add static private methods/fields support

    Type Narrowing

    class MyClass {
      #branded = true;
      prop = Math.random();
    
      equals(x: unknown) {
        if (typeof x === 'object' && #branded in x) {
          return x.prop === this.prop;
          //     ^ - should we narrow 'x' to 'MyClass' here?
        }
        return false;
      }
    }

    Pragmatically #branded in x could narrow right down from object to MyClass as in most cases the only way x could have the privateField is by being an instance of the class. Unlike 'prop' in x where there can easily be other classes that also have a 'prop' field. So this feels like it would be a nice optimisation for the developer experience.

    However this check can also be made to pass in slightly more esoteric code where x is not an instance of MyClass:

    class SuperReturn {
      constructor(x: any) { return x; }
    }
    
    class MyClass extends SuperReturn {
      #branded = true;
      
      constructor(x: unknown) {
        super(x);
      }
    
      static getBranded(x: MyClass) {
        return x.#branded;
      }
    }
    
    const a = {};
    new MyClass(a);
    // 'a' will now have the #branded private field
    assertTrue(MyClass.getBranded(a as any));
    // but is not an instance of the class
    assertFalse(a instanceOf MyClass);

    Do we want the type narrow to go for the (assumed) common case or for the correct case? Or maybe there is a middle ground e.g. only narrow when the classes does not extend another class (I believe this is the only way the check can be 'corrupted')

    Looking at instanceOf klass checks, these will narrow to klass, even though they can also be fooled by manually setting the proptype of an object. So seems like there is good precedence here already to 'do the pragmatic thing' instead of the 'correct' thing.

  3. sosoba commented on Aug 4, 2021

    @sosoba

    This feature has been marked by TC39 as finished.

    I have such questions.

    1. Currently configuration target=ES2021 means transpiling to WeakMap and target=ESNext leaves code unchanged. Will it stay this way or will ES2021 containt native private fileds or will ES2022 has been added?
    2. Is there a table in the documentation that maps the target profile to transpilation scenarry?
    3. Whether the title of this issue needs to be updated?

    Best regards.

  4. acutmore commented on Aug 4, 2021

    @acutmore
    Contributor

    ES2021 was finalised before this proposal went to stage 4 so it will be part of ES2022

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

    ES NextNew featurers for ECMAScript (a.k.a. ESNext)Help WantedYou can do thisSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions