microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Protected instance member inaccessible to same-class constructor via object destructuring assignment

jimmy-zhening-luo opened this issue Β· comments

πŸ”Ž Search Terms

class protected member constructor another instance access destructuring assignment same type typescript

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about each common issue, pre-declined request, behavior that looks wrong but is actually right, etc.

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEYD2A7AzgF3gNShAlsACpQDmAXPAK4r4COVCaAngLYBGSEA3ALABQoSLAQZmABwQlS8ALzxMMfChkAyeAG94AbVwFiZALqUMMRvAC+ffgOFo08AKpoQMTQPjxxVdgTDw4KGBUCGZ4AAsoFGAIEEpFZVJrT3EYJAxwDOB4AH0MMjRKaW1Daw9EVEUqMAwkNwAKcs8qFxhKZ1cAGib4FBAAd2k0AH5400SS7v5PAEp3ac94RoXF+dXVyOjYk3D8NAA6TZiQKfXFvIKdvf2L0jRO+AB6R-gAUQAld4B5d57PCzk8DEkiQADNegMhnJZPIAEQ0UCg5QgYCwv6LYbUVrozyUDQ49ZHbZY1yHKLHU5nda3QpOVr7MDhcAAayG9X2HL6gwKM0pVMsPRmAh6GF2ByJCHkLVJEuSi1F1xpgOBIDBEO5d2hcIRICRfVR6Mx0pgN3ymue8C+AGl0e16YyWWyOfsuUMZnKLMKFql8AA3KAZBT5DD4fwOsCsgrsjlEMZKFQlGZFMglNaLOAYKgwFDwIj7JEQDIwZZneoYOOJJNA+B7XNkOQAPgJGH2sRUovgDfgAAYHhbxr78Hh4CAAB5QVjiWKCj0CT38IA

πŸ’» Code

declare const ValidTag: unique symbol;
declare type Tag = string & { [ValidTag]: true };

class User {
  public readonly handle: string;
  protected _tags: Tag[];

  constructor (
    user: User,
    newTags?: string[],
  ) {
    (
      {
        handle: this.handle,
        _tags: this._tags, // ERROR
      } = typeof newTags === "undefined"
        ? user
        : {
            handle: user.handle,
            _tags: User.checkTags(...newTags),
          }
    )

    this.handle = user.handle;
    this._tags = typeof newTags === "undefined"
      ? user._tags // OK
      : User.checkTags(...newTags);
  }

  private static checkTags(...T: string[]): Tag[] {
    return T.filter(
      (t: string): t is Tag =>
        t.length > 0, // trivial example
    );
  }
}

πŸ™ Actual behavior

TypeScript compiler fails to transpile this code to JavaScript, throwing this error on line 15 of the sample code (labeled Case 1):

Property '_tags' does not exist on type 'User | { handle: string; _tags: Tag[]; }'.
  • Type System: This is counterintuitive, because the logic in Case 2 looks equivalent to the untrained eye (there may be caveats I’m unaware of), but TypeScript does not error in that case.
  • Runtime: Although irrelevant to TypeScript desired behavior (as readonly is solely a type decoration), ES6 JavaScript equivalent syntax runs as expected:
class Foo {
  a; b;
  constructor(foo, newB) {
    ({ a: this.a, b: this.b } = typeof newB === "undefined"
      ? foo
      : { a: foo.a, b: Foo.processB(newB) });
  }
  static processB(B) { return B }
}
let me = new Foo({ a: 1, b: 2 });
console.log(me);                // {a:1, b:2}
console.log(new Foo(me));       // {a:1, b:2}
console.log(new Foo(me, 9001)); // {a:1, b:9001}

πŸ™‚ Expected behavior

TypeScript compiler successfully transpiles this code to JavaScript.

Alternative A: TypeScript compiler provides a friendlier error message to help the user fail soft (which can also potentially be Quick Fixed by VSCode).

Alternative B: Won’t Fix as expected behavior, wherein the closed bug may serve as a handy reference for any future searchers on this topic.

Additional information about the issue

No response

Essentially duplicate of #9974.

Essentially duplicate of #9974.

What the… I am bad at searching. πŸ˜“ I will confirm in my example and then close as dupe.

I am expecting to see the following behavior if I declare a two interfaces A, B that mimic class User β€” A with protected _tags, and B undecorated or with public _tags:

  • new User() expects A | User: βœ…
  • new User() expects B | User: ❌

Does that sound right?

EDIT: Never mind. I think that’s nonsensical on my part; if I understand the original bug correctly, the problem would be that the (undecorated) destructuring assignment call is implicitly public, therefore inherently unable to destructure a type with the same properties at a different privacy level, correct?