Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions tsc/internal/bundled/libs/lib.es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,46 @@ interface ArrayLike<T> {
readonly [n: number]: T;
}

declare namespace inherit {
type _Func = (...args: any[]) => any;

// given number of normal parameters before the rest parameter, this gives the appropriate type for the rest parameter.
// there might be a way to define this recursively but for now it is only well defined for 0 to 4 arguments.
// otherwise this returns a string constant explaining limitation in implementation.
type _GetRestParam<F extends _Func, P extends number> = P extends 0 ? F extends ((...args: infer A)=>any) ? A : never
: P extends 1 ? F extends ((x0: any, ...args: infer A) => any) ? A : never
: P extends 2 ? F extends ((x0: any, x1: any, ...args: infer A) => any) ? A : never
: P extends 3 ? F extends ((x0: any, x1: any, x2:any, ...args: infer A) => any) ? A : never
: P extends 4 ? F extends ((x0: any, x1: any, x2:any, x3:any, ...args: infer A) => any) ? A : never
: "REST PARAMETERS ONLY IMPLEMENTED FOR UP TO 4 PRIOR ARGUMENTS"

type _KeysOfMethods<BaseType> = { [K in keyof BaseType]: BaseType[K] extends _Func ? K : never }[keyof BaseType];

}
/**
* demonstration of how the `inherit` keyword would work as working with explicit type arguments.
* @param BaseType - intersection of extended base class and all implemented interfaces
* @param Field - the field name or method name being used. if Pos is not "Field" this must point to a method.
* @param Pos - one of:
* "Field" for the type of the defined field (default if not given)
* "Return" for the return type of the method
* "Param" for a parameter of the method (Idx specifies position)
* "Rest" for a rest parameter (...args).
* @param Idx - when Pos is "Param" this is the position of the parameter,
* when Pos is "Rest" this is the number of parameters before the rest parameter.
*/
type inherit<BaseType, Field extends (Pos extends ("Param" | "Return" | "Rest") ? inherit._KeysOfMethods<BaseType> : keyof BaseType),
Pos extends "Field" | "Param" | "Return" | "Rest" = "Field",
Idx extends (Pos extends "Param" | "Rest" ? number : never) = never
> = Pos extends "Field" ? BaseType[Field]
: BaseType[Field] extends inherit._Func
? Pos extends "Return" ? ReturnType<BaseType[Field]>
: Pos extends "Param" ? Parameters<BaseType[Field]>[Idx]
: Pos extends "Rest" ? inherit._GetRestParam<BaseType[Field], Idx>
: never // case where Pos is not one of the 4 choices
: never; // case where BaseType[Field] is not a function for one of the choices that require a function


/**
* Make all properties in T optional
*/
Expand Down
143 changes: 143 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24138,6 +24138,139 @@ func (c *Checker) getTupleElementType(t *Type, index int) *Type {
return nil
}

// getInheritTypeArguments implements the `inherit` proof-of-concept. Given a `TypeReferenceNode`
// that refers to the global `inherit` type alias and was written with no explicit type arguments,
// it synthesizes the type arguments the alias expects based on where `inherit` appears:
//
// inherit<BaseType, Field, Pos, Idx>
//
// where:
// - BaseType is the intersection of the enclosing class's extends type and all implemented interfaces,
// - Field is a string-literal type of the member name,
// - Pos is one of "Field" | "Param" | "Return" | "Rest",
// - Idx is a number-literal type (parameter index for "Param", count of leading params for "Rest").
//
// It returns nil when `inherit` is used in a position that is not supported (e.g. outside a class, or
// not in a field/parameter/return type), so that the caller reports the normal diagnostic.
func (c *Checker) getInheritTypeArguments(node *ast.Node) []*Type {
// Determine the enclosing class and its base-type intersection.
classNode := ast.GetContainingClass(node)
if classNode == nil {
return nil
}
baseType := c.getInheritBaseType(classNode)
if baseType == nil {
return nil
}

// Classify the position of `inherit`. `inherit` may be the direct type of a declaration
// (`foo: inherit`) or nested inside a composed type (`foo: Readonly<inherit>`,
// `foo: inherit | "x"`). Walk up through any enclosing type nodes so that composition works;
// the walk stops at the first non-type ancestor, which should be the owning declaration.
cur := node
for cur.Parent != nil && ast.IsTypeNode(cur.Parent) {
cur = cur.Parent
}
parent := cur.Parent
if parent == nil {
return nil
}
stringArg := func(s string) *Type { return c.getStringLiteralType(s) }

switch {
case ast.IsPropertyDeclaration(parent) && parent.Type() == cur:
// `public foo: inherit` -> inherit<Base, "foo", "Field">
name := c.getInheritMemberName(parent)
if name == "" {
return nil
}
return []*Type{baseType, stringArg(name), stringArg("Field")}

case ast.IsParameterDeclaration(parent) && parent.Type() == cur:
// Parameter type: either a normal parameter or a rest parameter of a method.
paramNode := parent
method := paramNode.Parent
if method == nil || !ast.IsFunctionLikeDeclaration(method) {
return nil
}
name := c.getInheritMemberName(method)
if name == "" {
return nil
}
// A setter's parameter inherits the field type of the member, not a method parameter type.
// `set foo(v: inherit)` -> inherit<Base, "foo", "Field">
if ast.IsSetAccessorDeclaration(method) {
return []*Type{baseType, stringArg(name), stringArg("Field")}
}
params := method.Parameters()
index := -1
for i, p := range params {
if p == paramNode {
index = i
break
}
}
if index < 0 {
return nil
}
idxArg := c.getNumberLiteralType(jsnum.Number(index))
if paramNode.AsParameterDeclaration().DotDotDotToken != nil {
// `...rest: inherit` -> inherit<Base, name, "Rest", index>
return []*Type{baseType, stringArg(name), stringArg("Rest"), idxArg}
}
// `param: inherit` -> inherit<Base, name, "Param", index>
return []*Type{baseType, stringArg(name), stringArg("Param"), idxArg}

case ast.IsFunctionLikeDeclaration(parent) && parent.Type() == cur:
name := c.getInheritMemberName(parent)
if name == "" {
return nil
}
// A getter's return type inherits the field type of the member, not a method return type.
// `get foo(): inherit` -> inherit<Base, "foo", "Field">
if ast.IsGetAccessorDeclaration(parent) {
return []*Type{baseType, stringArg(name), stringArg("Field")}
}
// Method return type: `foo(): inherit` -> inherit<Base, name, "Return">
return []*Type{baseType, stringArg(name), stringArg("Return")}
}

return nil
}

// getInheritMemberName returns the text of a declaration's name, or "" if it is not a simple
// identifier/string name (e.g. a computed property name, which `inherit` does not support).
func (c *Checker) getInheritMemberName(decl *ast.Node) string {
name := decl.Name()
if name == nil {
return ""
}
switch name.Kind {
case ast.KindIdentifier, ast.KindStringLiteral, ast.KindNumericLiteral, ast.KindPrivateIdentifier:
return name.Text()
}
return ""
}

// getInheritBaseType computes the BaseType argument for `inherit`: the intersection of the class's
// extends type (if any) and all implemented interface types. Returns nil if the class neither
// extends nor implements anything.
func (c *Checker) getInheritBaseType(classNode *ast.Node) *Type {
classType := c.getDeclaredTypeOfSymbol(c.getSymbolOfDeclaration(classNode))
var baseTypes []*Type
baseTypes = append(baseTypes, c.getBaseTypes(classType)...)
for _, typeRefNode := range ast.GetImplementsHeritageClauseElements(classNode) {
t := c.getReducedType(c.getTypeFromTypeNode(typeRefNode))
if !c.isErrorType(t) {
baseTypes = append(baseTypes, t)
}
}
if len(baseTypes) == 0 {
return nil
}
return c.getIntersectionType(baseTypes)
}

/**
* Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include
* references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the
Expand All @@ -24160,6 +24293,16 @@ func (c *Checker) getTypeFromTypeAliasReference(node *ast.Node, symbol *ast.Symb
typeParameters := c.typeAliasLinks.Get(symbol).typeParameters
if len(typeParameters) != 0 {
numTypeArguments := len(typeArguments)
// `inherit` proof-of-concept: when the global `inherit` type alias is used with no explicit
// type arguments, synthesize them from the syntactic position in the enclosing class (base
// type(s), member name, position kind, and index) and instantiate the alias with those.
// If the position is not a valid use of `inherit`, fall through so the normal
// "requires N type arguments" diagnostic is reported.
if numTypeArguments == 0 && symbol.Name() == "inherit" {
if inheritArgs := c.getInheritTypeArguments(node); inheritArgs != nil {
return c.getTypeAliasInstantiation(symbol, inheritArgs, nil)
}
}
minTypeArgumentCount := c.getMinTypeArgumentCount(typeParameters)
if numTypeArguments < minTypeArgumentCount || numTypeArguments > len(typeParameters) {
message := core.IfElse(minTypeArgumentCount == len(typeParameters),
Expand Down
5 changes: 5 additions & 0 deletions tsc/internal/fourslash/tests/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,11 @@ var CompletionGlobalTypeDecls = []fourslash.CompletionsExpectedItem{
Kind: new(lsproto.CompletionItemKindClass),
SortText: new(string(ls.SortTextGlobalsOrKeywords)),
},
&lsproto.CompletionItem{
Label: "inherit",
Kind: new(lsproto.CompletionItemKindClass),
SortText: new(string(ls.SortTextGlobalsOrKeywords)),
},
&lsproto.CompletionItem{
Label: "ArrayLike",
Kind: new(lsproto.CompletionItemKindInterface),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
inheritTypeAliasPoC.ts(76,5): error TS2322: Type '"square"' is not assignable to type '"circle"'.
inheritTypeAliasPoC.ts(119,9): error TS2322: Type '"triangle"' is not assignable to type '"circle" | "square"'.
inheritTypeAliasPoC.ts(128,5): error TS2322: Type '"triangle"' is not assignable to type '"circle" | "square"'.
inheritTypeAliasPoC.ts(134,44): error TS2322: Type 'string' is not assignable to type 'boolean'.
inheritTypeAliasPoC.ts(140,15): error TS2322: Type 'number' is not assignable to type 'string'.
inheritTypeAliasPoC.ts(150,8): error TS2707: Generic type 'inherit' requires between 2 and 4 type arguments.
inheritTypeAliasPoC.ts(153,21): error TS2707: Generic type 'inherit' requires between 2 and 4 type arguments.


==== inheritTypeAliasPoC.ts (7 errors) ====
// Proof-of-concept test suite for the `inherit` type alias (issue #36165).
//
// `inherit` is a global type alias (defined in lib.es5.d.ts) that, when used with no explicit
// type arguments, is resolved contextually by the checker to the matching member type of the
// enclosing class's base class and/or implemented interfaces:
//
// - Instance field -> Base[Field]
// - Getter return / setter param -> Base[Field] (same as a field)
// - Method parameter -> Parameters<Base[Field]>[Index]
// - Method rest parameter -> trailing tuple after `Index` leading params
// - Method return value -> ReturnType<Base[Field]>
//
// where Base is the intersection of the extends type and all implemented interfaces.
//
// This file is both a demonstration and an executable spec. Cases marked "SHOULD RESOLVE" are the
// intended behavior; cases marked "SHOULD ERROR" document intentional constraints. Where the
// current PoC does not yet match the intended behavior (notably `inherit` nested inside a composed
// type such as `Readonly<inherit>` or `inherit | "x"`), the baseline will reveal the gap.

interface Base {
field: "square" | "circle";
foo(a: number, b: string): boolean;
bar(a: number, b: string, c?: any): boolean;
}

// ---------------------------------------------------------------------------
// 1a. implements an interface.
// ---------------------------------------------------------------------------
class AllPositions implements Base {
field: inherit = "square"; // SHOULD RESOLVE: "square" | "circle"
foo(a: inherit, b: inherit): inherit { // SHOULD RESOLVE: a:number, b:string, ret:boolean
return true;
}
bar(a: inherit, ...rest: inherit): inherit { // SHOULD RESOLVE: a:number, rest:[string, any?], ret:boolean
return true;
}
}


// ---------------------------------------------------------------------------
// 1b. extends base class.
// ---------------------------------------------------------------------------
class Geometry {
model: "square" | "circle" = "square";
render(scale: number): void {}
}
class Canvas extends Geometry {
model: inherit = "square"; // SHOULD RESOLVE: "square" | "circle"
render(scale: inherit): inherit {} // SHOULD RESOLVE: scale:number, ret:void
}

// ---------------------------------------------------------------------------
// 2. Multiple bases: extends + implements, intersected as (Base & A & B).
// ---------------------------------------------------------------------------
interface A { a: number; shared: string; }
interface B { b: "x" | "y"; shared: string; }
class BaseC { c: boolean = true; }
class Multi extends BaseC implements A, B {
a: inherit = 1; // SHOULD RESOLVE: number
b: inherit = "x"; // SHOULD RESOLVE: "x" | "y"
c: inherit = false; // SHOULD RESOLVE: boolean (from extends)
shared: inherit = "s"; // SHOULD RESOLVE: string (A & B agree)
}

// ---------------------------------------------------------------------------
// 3. Conflicting member types across interfaces narrow to their intersection.
// Foo.shape = "square" | "circle", Bar.shape = "circle" | "triangle"
// => (Foo & Bar)["shape"] = "circle"
// ---------------------------------------------------------------------------
interface Foo { shape: "square" | "circle"; }
interface Bar { shape: "circle" | "triangle"; }
class IntersectNarrow implements Foo, Bar {
shape: inherit = "circle"; // SHOULD RESOLVE: "circle" (only common member)
}
class IntersectNarrowBad implements Foo, Bar {
shape: inherit = "square"; // SHOULD ERROR: "square" not in the intersection "circle"
~~~~~
!!! error TS2322: Type '"square"' is not assignable to type '"circle"'.
}

// ---------------------------------------------------------------------------
// 4. Composition: inherit nested inside other type constructors.
// ---------------------------------------------------------------------------
interface Robot {
sensors: {sight?: boolean, hearing?: boolean, touch?: boolean};
doAction(action: "walk" | "turn"): void;
}
class FlyingRobot implements Robot {
sensors: Required<inherit> = {sight: true, hearing: false, touch: false}; // SHOULD RESOLVE: {sight: boolean, hearing: boolean, touch: boolean};
doAction(action: inherit | "fly"): inherit { // SHOULD RESOLVE: action:"walk"|"turn"|"fly"
}
}

// ---------------------------------------------------------------------------
// 5. Arrow-function field: field-level inherit, params inferred by assignment.
// ---------------------------------------------------------------------------
class ArrowField implements Base {
field: inherit = "square";
foo: inherit = (a, b) => true; // SHOULD RESOLVE: foo:(a:number,b:string)=>boolean
bar: inherit = (a, b, c) => true;
}

// ---------------------------------------------------------------------------
// 6. Accessors: a getter's return and a setter's parameter inherit the FIELD type.
// (Issue lists "Getter return value" and "Setter parameter" as supported positions.)
// ---------------------------------------------------------------------------
abstract class ShapeBase {
abstract kind: "square" | "circle";
}
class ShapeImpl extends ShapeBase {
private _kind: "square" | "circle" = "square";
get kind(): inherit { // SHOULD RESOLVE: "square" | "circle" (field type)
return this._kind;
}
set kind(v: inherit) { // SHOULD RESOLVE: "square" | "circle" (field type)
this._kind = v;
}
}
class ShapeImplBadGetter extends ShapeBase {
get kind(): inherit {
return "triangle"; // SHOULD ERROR: "triangle" not in "square" | "circle"
~~~~~~
!!! error TS2322: Type '"triangle"' is not assignable to type '"circle" | "square"'.
}
set kind(v: inherit) {}
}

// ---------------------------------------------------------------------------
// 7. Negative / assignment checks: inherit must produce the NARROW type, not `any`.
// ---------------------------------------------------------------------------
class WrongField implements Base {
field: inherit = "triangle"; // SHOULD ERROR: not "square" | "circle"
~~~~~
!!! error TS2322: Type '"triangle"' is not assignable to type '"circle" | "square"'.
foo(a: inherit, b: inherit): inherit { return true; }
bar(a: inherit, ...rest: inherit): inherit { return true; }
}
class WrongReturn implements Base {
field: inherit = "square";
foo(a: inherit, b: inherit): inherit { return "nope"; } // SHOULD ERROR: string not assignable to boolean
~~~~~~
!!! error TS2322: Type 'string' is not assignable to type 'boolean'.
bar(a: inherit, ...rest: inherit): inherit { return true; }
}
class WrongParamUse implements Base {
field: inherit = "square";
foo(a: inherit, b: inherit): inherit {
const s: string = a; // SHOULD ERROR: number not assignable to string
~
!!! error TS2322: Type 'number' is not assignable to type 'string'.
return true;
}
bar(a: inherit, ...rest: inherit): inherit { return true; }
}

// ---------------------------------------------------------------------------
// 8. Invalid positions / constraints.
// ---------------------------------------------------------------------------
class NoBase {
x: inherit = 1; // SHOULD ERROR: class has no base/interfaces
~~~~~~~
!!! error TS2707: Generic type 'inherit' requires between 2 and 4 type arguments.
}

type OutsideClass = inherit; // SHOULD ERROR: inherit used outside a class
~~~~~~~
!!! error TS2707: Generic type 'inherit' requires between 2 and 4 type arguments.

Loading