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

Allow to directly reference the types associated with interface properties #4555

Closed
rotemdan opened this issue Aug 31, 2015 · 6 comments
Closed
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@rotemdan
Copy link

interface A {
    prop: {
        x: number;
        y: number;
    }
}

Trying to associate the type of prop with a variable would lead to an error:

let p: A.prop; // Error: cannot find namespace A

Aside from anonymous object interfaces, it could also be allowed for function types:

interface A {
    func(arg: number): string
}

let f: A.func; // type of f is now func(arg: number): string
f = (arg: number) => "hello"; // OK

Primitive types:

interface A {
    prop: boolean;
}

let val: A.prop; // type of val is now boolean
val = true; // OK

And also when the types are nested:

interface A {
    outerProp: {
        innerProp: {
            x: number;
            y: number;
        }
    }
}

let p: A.outerProp.innerProp; // type of p would now be {x: number, y:number}
p = { x: 42, y: 24 } //OK

The current workaround (the best that could I find, at least [edit: I added an additional one that uses typeof in the next comment]) is to define the type of prop as a named interface or alternatively use the type keyword.

interface PropInterface {
    x: number;
    y: number;
}
// Or, alternatively:
type PropInterface = { x: number, y: number };

interface A {
    prop: PropInterface;
}

let p: PropInterface; // OK

But that isn't always necessary or elegant, especially if the types are nested or generic (short example below).

This can also be extended to types defined through the type keyword or when using typeof SomeClass. Or even with generics:

interface A<T> { 
    prop: { func: (val: T) => T } 
}

let f: A<number>.prop.func // working around this may be possible, but wouldn't be pretty.

A real-world use case I encountered was casting a value into the type of an interface member from a union that includes it (this happened when thinking about issues with assignability of merged/intersected types):

let merged: A merge B; // Also happens with intersection: A & B
let obj: A;
obj.prop = <A.prop> merged.prop; // the type of merged.prop is the union A.prop | B.prop

// Edit: It turns out that a relatively simple workaround is possible with the 'typeof' operator, 
// that is explained in the next comment:
obj.pro = <typeof obj.prop> merged.prop;

@RyanCavanaugh Too Complex? Perhaps at least keep it as an option for the future?

@rotemdan
Copy link
Author

I'll try to state it more formally:

The access path, starting with the name of the type e.g. A.outerProp.innerProp, would be used as an alias for the type the referenced property is associated with. Aside from anonymous types, this also includes cases where a property was set a primitive type (e.g. type A = { val: number }, A.val would now be an alias for number), or even a named type (type A = { obj: SomeObjType } A.obj would now be an alias for SomeObjType).

If that syntax appears to confuse with instance access operations (I'm not sure at this moment), it is possible to require the typeof keyword with it, e.g. let x: typeof A.outerProp.innerProp. The problem now is that typeof may be needed to be used twice to say, reference a class type: e.g. typeof (typeof SomeClass).outerProp.innerProp. This also may turn out to be a misuse of the semantics of "typeof" because it was originally intended be used with instances, not types (If I'm not mistaken).

Mentioning typeof, I now realize there's another way to work around this, but it would require a "dummy" instance of the interface:

interface A {
    prop: {
        x: number;
        y: number;
    }
}

let a: A;
let p: typeof a.prop; // OK, type of p is { x: number, y: number }

But that looks a bit like a hack to me.. (not a good practice and not something I would recommend as a standard solution). What this may imply though is that this proposal may not be Too Complex™ because that "hack" could be used internally in the compiler to retrieve the type (at least in a large part of the cases). There are also places where it would be impossible to use dummies or would look really bad: like in outermost types (dummies would either be impossible or need to be global variables), declaration files (dummies would look inappropriate for a "formal" declaration) etc.

So, in the light of this I would say that it still seems reasonable and more natural to me to have a way to directly access types of properties from the containing type itself, rather than indirectly through an instance, and that may not turn out to be "Too Complex" to implement, but I'm not sure right now about what is the best, most readable syntax to for it.

@rotemdan rotemdan changed the title Allow referencing anonymous types set on interface properties Allow to directly reference the types associated with interface properties Aug 31, 2015
@rotemdan
Copy link
Author

Some creative (and unexpected) applications of this (which also wouldn't be possible with the typeof dummy "trick"):

Directly referencing the type of a different property of the containing interface in a method's argument or return type.

interface Cool {
  val: SomeType;

  // The return type would automatically "sync" with whatever type "val" has been set to!
  func(arg: number): Cool.val;
}

Automatic type matching between interface properties (both in the same one and on different ones):

interface A {
    prop: {
        subProp: {
            x: number,
            y: number,
            str: string
        }
    }
}

interface B {
    // would automatically "sync" with changes to the type of A.prop.subProp
    matchedProp: A.prop.subProp;
}

I will post others when I can think of more, but I already find these interesting.

@mhegazy mhegazy added the Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. label Aug 31, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Aug 31, 2015

This proposal would be ambiguous given the current language semantics. consider the case with a module with a property with the same name, e.g.:

interface A {
    I: string;
}
namespace A {
   export interface I {}
}

var x: A.I; // A.I can not resolve to property I of interface A, as it already resolves to something else.

I believe what you are asking for is more along the lines of operators like typeof, nameof(#1579), returnTypeOf(#4233 (comment)), so i would expect you want something like propertyTypeOf

@rotemdan
Copy link
Author

@mhegazy

Having an interface and a namespace with the same name is a "corner" of the language that I'm not very familiar with (I do think I encountered it once or twice so I had a feeling it exists). I wasn't very sure if having a direct reference like A.prop would be very readable anyway. Using a keyword like propertyTypeOf looks like an interesting approach, and will also make the semantics clearer. I will take some time to consider this one and perhaps alternatives.

I believe I can write a proposal for this. I'm not sure if I could cover everything but I'll try as much as I can.

@mhegazy mhegazy added the Suggestion An idea for TypeScript label Aug 31, 2015
@rotemdan
Copy link
Author

rotemdan commented Sep 1, 2015

@mhegazy

I considered several approaches to the syntax. One was without a keyword, using something like let val: { SomeInterface.prop }, i.e. using the curly brackets to signify a "link" (similar to a file system "link" to meta-information), but I eventually felt that was too confusing with the interface syntax and overall adds too much "noise" with the curly brackets, and may turn out to conflict with other or future features of the language.

Then I went back to look for a keyword but wanted something that's shorter than propertyTypeOf and possibly wouldn't require capitalization.

I came across the idea of using typeon which would also more closely parallel typeof (after all, they are almost the same).

interface MyInterface {
    myProp: {
        x: number,
        y: number
    }
}

let x: typeon MyInterface.myProp;

The idea is that the operator queries for the type is set "on" that property, instead of belonging to it ("of") as would be with an instance.

This may not be perfect but I went through an exhaustive list of prepositions in English (and another one here). The only somewhat reasonable alternatives that I could find were "in" (i.e. typein) and "at" (i.e. typeat), but "on" looked like the best so far.

It is still possible there are better options so if you (or anyone else) have alternative ideas then please suggest.

[edit: typefor is also an alternative]

@rotemdan
Copy link
Author

rotemdan commented Sep 5, 2015

The proposal is in #4640, closing.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

2 participants