Skip to content

Commit cfbaef2

Browse files
committed
use generaal function, support optional chain.
1 parent e5054c5 commit cfbaef2

File tree

3 files changed

+98
-126
lines changed

3 files changed

+98
-126
lines changed

src/compiler/checker.ts

Lines changed: 98 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20956,57 +20956,82 @@ namespace ts {
2095620956
return type;
2095720957
}
2095820958

20959+
// the expression should be like a.b?.c?.d.e.f or a
20960+
// a is an access expression.
20961+
// in which condition a.expression is not access expression if it is not the root?
20962+
function isAccessExpressionContainOptionalChain(e:UnaryExpression){
20963+
let tmp:UnaryExpression =e;
20964+
while(isAccessExpression(tmp)){
20965+
if(tmp.flags & NodeFlags.OptionalChain)
20966+
return true;
20967+
tmp = tmp.expression;
20968+
}
20969+
return false;
20970+
}
20971+
2095920972
// Expression could be TypeOfExpression, discriminate expression could be added further.
2096020973
// For now, user must make sure that type is one on the path of expression.
2096120974
// for example, typeof a.b.c.d.e, type could be the type of a or b or ... or e.
2096220975
// ATTENTION! in some case, should not narrow type according to the whole path, for example:
2096320976
// typeof a.b?.c.d? !== number, === undefined, !==undefined, when we come to a, its type could only be narrowed by a.b and nothing more.
2096420977
// But for typeof a.b?.c.d? === number, when we come to a, it could be narrowed by the full path.
20965-
function narrowUnionTypeWithPropertyPathAndExpression(type: UnionType, accessExpressionWithOutKeyword: Expression, _assumeTrue: boolean): Type[] | undefined {
20978+
// Match reference should be a special case, which is not handled by this function.
20979+
/**
20980+
* @param optionalChainSlice If this is true, only match the part before first optional chain. if expression is a.b.c?.d.e, only take a.b.c to narrow.
20981+
*/
20982+
function narrowUnionTypeWithPropertyPathAndExpression(type: UnionType, accessExpressionWithOutKeyword: Expression, optionalChainSlice: boolean=false): Type[] | undefined {
2096620983
// first, judge whether path is accessable to all type.
2096720984
// second, use expression to filter correct types
2096820985
// third, return filtered types.
2096920986

2097020987
// return one non-negative number if match
20971-
function getTypeDepthIfMatch(expression: Expression, type: Type): number {
20988+
// type(Type) a.b.c
20989+
// reference(Node) a.b.c
20990+
// expression(Node) a.b.c?.d.e
20991+
// I think type(Type) is better than reference(Node), but it meets some conditions, especially when expression contians optional chain. a?.b would add undefined to b and it is not b. maybe we could use isTypeSubtypeOf? would this meet some other strange condition?
20992+
//
20993+
function getTypeDepthIfMatch(expression: Expression, _type: Type): number {
2097220994
let result = 0;
2097320995
let exprTmp = expression;
20974-
let curType;
20975-
20976-
let exprTmp2 = expression;
20977-
const p :__String[] = [];
20978-
while(isAccessExpression(exprTmp2)){
20979-
p.unshift((<PropertyAccessExpression>exprTmp).name.escapedText);
20980-
exprTmp2 = exprTmp2.expression;
20981-
}
20982-
const root = exprTmp2;
20983-
const roottype = getTypeOfExpression(root);
20984-
const ttt = getTypeOfPropertyOfType(roottype,p[0]);
20985-
ttt;
20986-
// ?. would add undefined type. How to deal with it?
20987-
curType = getTypeOfExpression(expression);
20988-
20989-
if (curType === type) {
20990-
return result;
20991-
}
2099220996

20993-
while (exprTmp.kind === SyntaxKind.PropertyAccessExpression || exprTmp.kind === SyntaxKind.ElementAccessExpression) {
20994-
result = result + 1;
20995-
exprTmp = (<AccessExpression>exprTmp).expression;
20996-
curType = getTypeOfExpression(exprTmp);
2099720997

20998-
if (curType === type) {
20998+
while(isAccessExpression(exprTmp)){
20999+
result = result +1 ;
21000+
exprTmp = exprTmp.expression;
21001+
if(isMatchingReference(reference, exprTmp))
21002+
{
2099921003
return result;
2100021004
}
2100121005
}
21006+
21007+
// use type and expression
21008+
// {
21009+
// let curType;
21010+
// // ?. would add undefined type. How to deal with it?
21011+
// curType = getTypeOfExpression(expression);
21012+
21013+
// if (curType === type) {
21014+
// return result;
21015+
// }
21016+
21017+
// while (exprTmp.kind === SyntaxKind.PropertyAccessExpression || exprTmp.kind === SyntaxKind.ElementAccessExpression) {
21018+
// result = result + 1;
21019+
// exprTmp = (<AccessExpression>exprTmp).expression;
21020+
// curType = getTypeOfExpression(exprTmp);
21021+
21022+
// if (curType === type) {
21023+
// return result;
21024+
// }
21025+
// }
21026+
// }
2100221027
return -1;
2100321028
}
2100421029

2100521030
// If expression is a.b["c"].d, the result would be ["b","c","d"]
2100621031
// NOTE: If element expression is not known in compile progress like a.b[f()].d, the result would be undefined
2100721032
// //NOTE: this function need improvement, ElementAccessExpression argument might could be known in compile time, like "1"+"2", we should check "12" in the path, but how to get the value?
2100821033
// if given depth, the array.length would be the length. --- this property need have pre-knowledge of expression.
21009-
function getPropertyPathsOfAccessExpression(expressionOri: AccessExpression, depth?: number): __String[] | undefined {
21034+
function getPropertyPathsOfAccessExpression(expressionOri: AccessExpression, depth: number): __String[] | undefined {
2101021035
const properties = [];
2101121036
let exprTmp: LeftHandSideExpression = expressionOri;
2101221037
let propName: __String;
@@ -21051,35 +21076,14 @@ namespace ts {
2105121076
}
2105221077
return result;
2105321078
}
21054-
// is it must be ObjectType? For it is not primitive type?
21055-
let currentSymbol = nonUntionType.symbol;
2105621079

21057-
function tryGetPropertySymbolFromSymbol(s: Symbol, propertyName: __String) {
21058-
if (s.flags & SymbolFlags.Interface) {
21059-
return s.members?.get(propertyName);
21060-
}
21061-
if (s.flags & SymbolFlags.Property) {
21062-
if (s.valueDeclaration.kind === SyntaxKind.PropertySignature) {
21063-
const typeNode = (<PropertySignature>s.valueDeclaration).type;
21064-
if (typeNode?.kind === SyntaxKind.TypeLiteral) {
21065-
//@ts-ignore
21066-
const targetPropertyNode = (<TypeLiteralNode>typeNode).members.find(m => m?.name.text === propertyName);
21067-
if (targetPropertyNode) { return targetPropertyNode.symbol; }
21068-
}
21069-
}
21070-
debugger;
21071-
}
21072-
if (s.flags & SymbolFlags.TypeLiteral) {
21073-
return s.members?.get(propertyName);
21074-
}
21075-
debugger;
21076-
}
21077-
21078-
let i = 0;
21079-
while (i < pathsLength) {
21080+
let curType = nonUntionType;
21081+
for(let i = 0; i<pathsLength;i++){
2108021082
const path = paths[i];
21081-
const nextSymbol = tryGetPropertySymbolFromSymbol(currentSymbol, path);
21082-
// only the property explicitly accessable is considered now.
21083+
const nonNullableTypeIfStrict = getNonNullableTypeIfNeeded(curType);
21084+
const nextSymbol = getPropertyOfType(nonNullableTypeIfStrict,path);
21085+
21086+
// if it is not accessable to all types, break;
2108321087
if (!nextSymbol) {
2108421088
break;
2108521089
}
@@ -21089,20 +21093,36 @@ namespace ts {
2108921093
result = type;
2109021094
break;
2109121095
}
21092-
// if it is not last path, and is union or intersection, do not deal with this condition now.
21093-
// this could be improved, if union and all types has the property, it could go on. But it is further concerned.
21094-
if (!(type.flags & TypeFlags.Primitive) && (type.flags & TypeFlags.Union || type.flags & TypeFlags.Intersection)) { // this could be improved, if union and all types has the property, it could go on.
21095-
break;
21096-
}
21097-
currentSymbol = nextSymbol;
21098-
i = i + 1;
21096+
curType = type;
2109921097
}
21098+
2110021099
return result;
2110121100
}
2110221101

21103-
// for now, TypeOfExpression is like ```typeof a.b.c.e```, need this to remove typeof.
21104-
const expressionWithOutKeyword = accessExpressionWithOutKeyword;
21102+
function tmpExpressionWithOutOptionalChain(e:Expression){
21103+
let i =0;
21104+
let delta=0;
21105+
let tmp1 = e;
21106+
while(isAccessExpression(tmp1)){
21107+
i=i+1;
21108+
delta+=1;
21109+
if(tmp1.flags & NodeFlags.OptionalChain)
21110+
{delta=0}
21111+
tmp1 = tmp1.expression
21112+
}
21113+
i=i-delta;
21114+
for(let j=0;j<i;j++){
21115+
e = (<AccessExpression>e).expression;
21116+
}
21117+
return e;
21118+
}
21119+
21120+
if(optionalChainSlice){
21121+
accessExpressionWithOutKeyword = tmpExpressionWithOutOptionalChain(accessExpressionWithOutKeyword);
21122+
}
2110521123

21124+
const expressionWithOutKeyword = accessExpressionWithOutKeyword;
21125+
// getTypeOfNode
2110621126
// check some condition, if not meet, it means we could not handle this confition.
2110721127
if ((expressionWithOutKeyword.kind !==SyntaxKind.Identifier && !isAccessExpression(expressionWithOutKeyword))){// || (<AccessExpression>expressionWithOutKeyword).expression.kind === SyntaxKind.ThisKeyword) {
2110821128
return undefined;
@@ -21144,7 +21164,7 @@ namespace ts {
2114421164
if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
2114521165
assumeTrue = !assumeTrue;
2114621166
}
21147-
const facts = assumeTrue ?
21167+
let facts = assumeTrue ?
2114821168
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
2114921169
typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
2115021170
const target = getReferenceCandidate(typeOfExpr.expression);
@@ -21153,7 +21173,21 @@ namespace ts {
2115321173
return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2115421174
}
2115521175
if (type.flags & TypeFlags.Union) {
21156-
const propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression, assumeTrue);
21176+
let propertyTypeArray:Type[]|undefined;
21177+
21178+
const isExpressionContainOptionalChain = isAccessExpressionContainOptionalChain(typeOfExpr.expression);
21179+
// !== number, === undefined, !==undefined
21180+
if(assumeTrue && literal.text !== "undefined"){
21181+
// use full expression to narrow
21182+
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression, false);
21183+
}
21184+
else{
21185+
// use non-OptionalChain part to narrow
21186+
propertyTypeArray = narrowUnionTypeWithPropertyPathAndExpression(<UnionType>type, typeOfExpr.expression, true);
21187+
if(isExpressionContainOptionalChain){
21188+
facts = TypeFacts.All;
21189+
}
21190+
}
2115721191
if (!propertyTypeArray) {
2115821192
return type;
2115921193
}

tests/baselines/reference/typeGuardAccordingToPropertyDeep.errors.txt

Lines changed: 0 additions & 62 deletions
This file was deleted.

tests/baselines/reference/typeGuardAccordingToPropertyDeep.errors.txt.delete

Whitespace-only changes.

0 commit comments

Comments
 (0)