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

Proposal: Friend Declarations for classes #7692

Open
rbuckton opened this issue Mar 26, 2016 · 6 comments
Open

Proposal: Friend Declarations for classes #7692

rbuckton opened this issue Mar 26, 2016 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@rbuckton
Copy link
Member

NOTE: This proposal is an alternative to the proposal in #5228.

Overview

Often there is a need to share information on types within a program or package that should not be
accessed from outside of the program or package. While the public accessibility modifier allows
types to share information, is insufficient for this case as consumers of the package have access
to the information. While the private accessibility modifier prevents consumers of the package
from accessing information from the type, it is insufficient for this case as types within the
package also cannot access the information. To satisfy this case, we propose the addition of a FriendDeclaration to classes:

class A {
  friend createA;
  private constructor() { }
}

function createA() {
  return new A();
}

A FriendDeclaration is a new declaration supported in the body of a ClassDeclaration or ClassExpression that indicates that the class, function, or namespace to which the supplied name resolves can treat the private and protected members of the class as if they were public.

The name referenced in the FriendDeclaration is resolved relative to the ClassDeclaration. The name must be in scope.

Grammar

  ClassElement: ( Modified )
   FriendDeclaration

  FriendDeclaration:
   friendFriendDeclarationNames;

  FriendDeclarationNames:
   EntityName
   FriendDeclarationNames,EntityName

Examples

Instantiate a class with a private constructor:

class A {
  friend createA;
  private constructor() { }
}

function createA() {
  return new A();
}

Extend a class with a private constructor:

class A {
  friend B;
  private constructor() { }
}

class B extends A {
}

Access private members of an imported class:

// a.ts
import { update } from "./b";
export class A {
  friend update;
  private x: number;
}

// b.ts
import { A } from "./a";
export function update(a: A) {
  a.x = 1;
}
@rbuckton rbuckton added the Suggestion An idea for TypeScript label Mar 26, 2016
@mhegazy
Copy link
Contributor

mhegazy commented Mar 26, 2016

duplicate of #2136

@RyanCavanaugh
Copy link
Member

#5228 is the target dupe of that one, but I assume Ron knows the difference between the two

@basarat
Copy link
Contributor

basarat commented Mar 30, 2016

The desire of friend functions is demonstrated here : https://github.com/tildeio/glimmer#friend-properties-and-methods

Their workaround:

class Layout {
  private template: Template;
}

function compile(layout: Layout, environment: Environment): CompiledBlock {
  return layout['template'].compile(environment);
}

This proposal:

class Layout {
  friend compile;
  private template: Template;
}

function compile(layout: Layout, environment: Environment): CompiledBlock {
  return layout.template.compile(environment);
}

Personal view

One of the examples presented:

// a.ts
import { update } from "./b";
export class A {
  friend update;
  private x: number;
}

// b.ts
import { A } from "./a";
export function update(a: A) {
  a.x = 1;
}

Has a cyclic dependency :-/

@lifenautjoe
Copy link

I've made a new proposal which could also solve this. Please check it out. Would love to hear some thoughts on it. #19151

@Lusito
Copy link

Lusito commented Nov 1, 2017

For anyone struggling with this today, this is how I currently work around the issue.

First I create an interface with the friendly methods and properties:

export class B {
    protected add(x: number, y: number): number {
        return x + y;
    }
}
interface FriendlyB {
    add: (x: number, y: number) => number
}

If possible, I keep the interface only in one single module without exporting it.
Then wherever I need the friendly access, I do:

let b = new B();
let theAnswer = ((b as any) as FriendlyB).add(20, 22);
console.log(theAnswer);

Since the casting is compile-time, there is no overhead.
You need to be sure b is actually B, as the (b as any) removes type-checking until it is cast to FriendlyB. Also you need to be sure the interface actually matches your class.
But if you keep those places to a minimum and are careful when updating them, it might just help you out until TypeScript has friend support.

@GrantGryczan
Copy link

It's been a few years, so any updates? I'd love to see this because the current best alternative is to make my private fields public, resulting in outsiders being able to access fields that they really shouldn't be accessing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants