Skip to content
Open
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
2 changes: 1 addition & 1 deletion internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3084,7 +3084,7 @@ func (c *Checker) getTypePredicateParent(node *ast.Node) *ast.SignatureDeclarati
parent := node.Parent
switch parent.Kind {
case ast.KindArrowFunction, ast.KindCallSignature, ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindFunctionType,
ast.KindMethodDeclaration, ast.KindMethodSignature:
ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindGetAccessor:
if node == parent.Type() {
return parent
}
Expand Down
58 changes: 54 additions & 4 deletions internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,13 @@ func (c *Checker) narrowType(f *FlowState, t *Type, expr *ast.Node, assumeTrue b
}
fallthrough
case ast.KindThisKeyword, ast.KindSuperKeyword, ast.KindPropertyAccessExpression, ast.KindElementAccessExpression:
if predicate := c.getTypePredicateOfPropertyAccess(expr); predicate != nil && predicate.kind == TypePredicateKindThis {
if c.isOrContainsMatchingReference(f.reference, expr.Expression()) {
if narrowedType := c.narrowTypeByTypePredicate(f, t, predicate, expr, assumeTrue); narrowedType != t {
return narrowedType
}
}
}
return c.narrowTypeByTruthiness(f, t, expr, assumeTrue)
case ast.KindCallExpression:
return c.narrowTypeByCallExpression(f, t, expr, assumeTrue)
Expand Down Expand Up @@ -2420,19 +2427,62 @@ func (c *Checker) typeMaybeAssignableTo(source *Type, target *Type) bool {

func (c *Checker) getTypePredicateArgument(predicate *TypePredicate, callExpression *ast.Node) *ast.Node {
if predicate.kind == TypePredicateKindIdentifier || predicate.kind == TypePredicateKindAssertsIdentifier {
arguments := callExpression.Arguments()
if predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) {
return arguments[predicate.parameterIndex]
if ast.IsCallExpression(callExpression) || ast.IsNewExpression(callExpression) {
arguments := callExpression.Arguments()
if predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) {
return arguments[predicate.parameterIndex]
}
}
} else {
} else if ast.IsCallExpression(callExpression) {
invokedExpression := ast.SkipParentheses(callExpression.Expression())
if ast.IsAccessExpression(invokedExpression) {
return ast.SkipParentheses(invokedExpression.Expression())
}
} else if ast.IsPropertyAccessExpression(callExpression) || ast.IsElementAccessExpression(callExpression) {
return ast.SkipParentheses(callExpression.Expression())
}
return nil
}

func (c *Checker) getTypePredicateOfPropertyAccess(node *ast.Node) *TypePredicate {
if !ast.IsPropertyAccessExpression(node) && !ast.IsElementAccessExpression(node) {
return nil
}
objectExpr := node.Expression()
if objectExpr.Kind == ast.KindSuperKeyword {
return nil
}
var objectType *Type
if ast.IsOptionalChain(node) {
leftType := c.checkExpression(objectExpr)
objectType = c.GetNonNullableType(c.getOptionalExpressionType(leftType, objectExpr))
} else {
objectType = c.GetNonNullableType(c.checkExpression(objectExpr))
}
propName, ok := c.getAccessedPropertyName(node)
if !ok {
return nil
}
prop := c.getPropertyOfType(objectType, propName)
if prop == nil || prop.Flags&ast.SymbolFlagsGetAccessor == 0 {
return nil
}
getter := ast.GetDeclarationOfKind(prop, ast.KindGetAccessor)
if getter == nil {
return nil
}
typeNode := getter.Type()
if typeNode == nil || !ast.IsTypePredicateNode(typeNode) {
return nil
}
sig := c.getSignatureFromDeclaration(getter)
predicate := c.getTypePredicateOfSignature(sig)
if predicate == nil || predicate.kind != TypePredicateKindThis {
return nil
}
return predicate
}

func (c *Checker) getFlowTypeInConstructor(symbol *ast.Symbol, constructor *ast.Node) *Type {
var accessName *ast.Node
if strings.HasPrefix(symbol.Name, ast.InternalSymbolNamePrefix+"#") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//// [tests/cases/compiler/typeGuardGetterTypePredicate.ts] ////

=== typeGuardGetterTypePredicate.ts ===
class Session {
>Session : Symbol(Session, Decl(typeGuardGetterTypePredicate.ts, 0, 0))

user: string | null = null;
>user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))

get hasUser(): this is { user: string } {
>hasUser : Symbol(Session.hasUser, Decl(typeGuardGetterTypePredicate.ts, 1, 31))
>user : Symbol(user, Decl(typeGuardGetterTypePredicate.ts, 3, 28))

return this.user !== null;
>this.user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))
>this : Symbol(Session, Decl(typeGuardGetterTypePredicate.ts, 0, 0))
>user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))
}

hasUserMethod(): this is { user: string } {
>hasUserMethod : Symbol(Session.hasUserMethod, Decl(typeGuardGetterTypePredicate.ts, 5, 5))
>user : Symbol(user, Decl(typeGuardGetterTypePredicate.ts, 7, 30))

return this.user !== null;
>this.user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))
>this : Symbol(Session, Decl(typeGuardGetterTypePredicate.ts, 0, 0))
>user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))
}
}

const session = new Session();
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>Session : Symbol(Session, Decl(typeGuardGetterTypePredicate.ts, 0, 0))

if (session.hasUser) {
>session.hasUser : Symbol(Session.hasUser, Decl(typeGuardGetterTypePredicate.ts, 1, 31))
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>hasUser : Symbol(Session.hasUser, Decl(typeGuardGetterTypePredicate.ts, 1, 31))

session.user.toUpperCase();
>session.user.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>session.user : Symbol(user, Decl(typeGuardGetterTypePredicate.ts, 0, 15), Decl(typeGuardGetterTypePredicate.ts, 3, 28))
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>user : Symbol(user, Decl(typeGuardGetterTypePredicate.ts, 0, 15), Decl(typeGuardGetterTypePredicate.ts, 3, 28))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}

if (session.hasUserMethod()) {
>session.hasUserMethod : Symbol(Session.hasUserMethod, Decl(typeGuardGetterTypePredicate.ts, 5, 5))
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>hasUserMethod : Symbol(Session.hasUserMethod, Decl(typeGuardGetterTypePredicate.ts, 5, 5))

session.user.toUpperCase();
>session.user.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>session.user : Symbol(user, Decl(typeGuardGetterTypePredicate.ts, 0, 15), Decl(typeGuardGetterTypePredicate.ts, 7, 30))
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>user : Symbol(user, Decl(typeGuardGetterTypePredicate.ts, 0, 15), Decl(typeGuardGetterTypePredicate.ts, 7, 30))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}

if (!session.hasUser) {
>session.hasUser : Symbol(Session.hasUser, Decl(typeGuardGetterTypePredicate.ts, 1, 31))
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>hasUser : Symbol(Session.hasUser, Decl(typeGuardGetterTypePredicate.ts, 1, 31))

session.user; // string | null
>session.user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))
>session : Symbol(session, Decl(typeGuardGetterTypePredicate.ts, 12, 5))
>user : Symbol(Session.user, Decl(typeGuardGetterTypePredicate.ts, 0, 15))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//// [tests/cases/compiler/typeGuardGetterTypePredicate.ts] ////

=== typeGuardGetterTypePredicate.ts ===
class Session {
>Session : Session

user: string | null = null;
>user : string | null

get hasUser(): this is { user: string } {
>hasUser : boolean
>user : string

return this.user !== null;
>this.user !== null : boolean
>this.user : string | null
>this : this
>user : string | null
}

hasUserMethod(): this is { user: string } {
>hasUserMethod : () => this is { user: string; }
>user : string

return this.user !== null;
>this.user !== null : boolean
>this.user : string | null
>this : this
>user : string | null
}
}

const session = new Session();
>session : Session
>new Session() : Session
>Session : typeof Session

if (session.hasUser) {
>session.hasUser : boolean
>session : Session
>hasUser : boolean

session.user.toUpperCase();
>session.user.toUpperCase() : string
>session.user.toUpperCase : () => string
>session.user : string
>session : Session & { user: string; }
>user : string
>toUpperCase : () => string
}

if (session.hasUserMethod()) {
>session.hasUserMethod() : boolean
>session.hasUserMethod : () => this is { user: string; }
>session : Session
>hasUserMethod : () => this is { user: string; }

session.user.toUpperCase();
>session.user.toUpperCase() : string
>session.user.toUpperCase : () => string
>session.user : string
>session : Session & { user: string; }
>user : string
>toUpperCase : () => string
}

if (!session.hasUser) {
>!session.hasUser : boolean
>session.hasUser : boolean
>session : Session
>hasUser : boolean

session.user; // string | null
>session.user : string | null
>session : Session
>user : string | null
}

28 changes: 28 additions & 0 deletions testdata/tests/cases/compiler/typeGuardGetterTypePredicate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @strict: true
// @noEmit: true

class Session {
user: string | null = null;

get hasUser(): this is { user: string } {
return this.user !== null;
}

hasUserMethod(): this is { user: string } {
return this.user !== null;
}
}

const session = new Session();

if (session.hasUser) {
session.user.toUpperCase();
}

if (session.hasUserMethod()) {
session.user.toUpperCase();
}

if (!session.hasUser) {
session.user; // string | null
}