-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Weak type detection #16047
Weak type detection #16047
Changes from 17 commits
bdcc4eb
3c76c3e
beba5de
e570775
c583c32
396071b
49d4aca
2433f56
c9da705
463e385
4bab55f
4b1f1b6
0b911d5
d1d487c
548f92a
f9a05a1
343572e
04c26b7
2100e40
3e4b83e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2285,7 +2285,7 @@ namespace ts { | |
Debug.assert(typeNode !== undefined, "should always get typenode?"); | ||
const options = { removeComments: true }; | ||
const writer = createTextWriter(""); | ||
const printer = createPrinter(options, writer); | ||
const printer = createPrinter(options); | ||
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); | ||
printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); | ||
const result = writer.getText(); | ||
|
@@ -8701,6 +8701,7 @@ namespace ts { | |
let expandingFlags: number; | ||
let depth = 0; | ||
let overflow = false; | ||
let disableWeakTypeErrors = false; | ||
|
||
Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); | ||
|
||
|
@@ -8977,17 +8978,35 @@ namespace ts { | |
} | ||
} | ||
|
||
function typeRelatedToEachType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { | ||
function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean): Ternary { | ||
let result = Ternary.True; | ||
const targetTypes = target.types; | ||
const saveDisableWeakTypeErrors = disableWeakTypeErrors; | ||
disableWeakTypeErrors = true; | ||
for (const targetType of targetTypes) { | ||
const related = isRelatedTo(source, targetType, reportErrors); | ||
if (!related) { | ||
disableWeakTypeErrors = saveDisableWeakTypeErrors; | ||
return Ternary.False; | ||
} | ||
result &= related; | ||
} | ||
return result; | ||
disableWeakTypeErrors = saveDisableWeakTypeErrors; | ||
return reportAssignmentToWeakIntersection(source, target, reportErrors) ? Ternary.False : result; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment on what you're doing here and below - the intent is obvious now, but it won't be to a random team member in 3 months. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I refactored this into a separate function and a named boolean. I think the two new names should serve as adequate documentation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may miss something here ->why would we call "reportAssignmentToWeakIntersection" without checking first that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that you always want to check the combined intersection type. Put another way, I assume that the only reason that tl;dr It doesn't need to check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oic. Jsut to make sure I understand this correctly because |
||
} | ||
|
||
function reportAssignmentToWeakIntersection(source: Type, target: IntersectionType, reportErrors: boolean) { | ||
const needsWeakTypeCheck = source !== globalObjectType && getPropertiesOfType(source).length > 0 && every(target.types, isWeakType); | ||
if (!needsWeakTypeCheck) { | ||
return false; | ||
} | ||
const hasSharedProperty = forEach( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: please add comment on why you don't use |
||
getPropertiesOfType(source), | ||
p => isKnownProperty(target, p.name, /*isComparingJsxAttributes*/ false)); | ||
if (!hasSharedProperty && reportErrors) { | ||
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source)); | ||
} | ||
return !hasSharedProperty; | ||
} | ||
|
||
function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean): Ternary { | ||
|
@@ -9277,8 +9296,12 @@ namespace ts { | |
let result = Ternary.True; | ||
const properties = getPropertiesOfObjectType(target); | ||
const requireOptionalProperties = relation === subtypeRelation && !(getObjectFlags(source) & ObjectFlags.ObjectLiteral); | ||
let foundMatchingProperty = !isWeakType(target); | ||
for (const targetProp of properties) { | ||
const sourceProp = getPropertyOfType(source, targetProp.name); | ||
if (sourceProp) { | ||
foundMatchingProperty = true; | ||
} | ||
|
||
if (sourceProp !== targetProp) { | ||
if (!sourceProp) { | ||
|
@@ -9329,7 +9352,10 @@ namespace ts { | |
} | ||
return Ternary.False; | ||
} | ||
const saveDisableWeakTypeErrors = disableWeakTypeErrors; | ||
disableWeakTypeErrors = false; | ||
const related = isRelatedTo(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp), reportErrors); | ||
disableWeakTypeErrors = saveDisableWeakTypeErrors; | ||
if (!related) { | ||
if (reportErrors) { | ||
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); | ||
|
@@ -9355,9 +9381,30 @@ namespace ts { | |
} | ||
} | ||
} | ||
if (!foundMatchingProperty && !disableWeakTypeErrors && source !== globalObjectType && getPropertiesOfType(source).length > 0) { | ||
if (reportErrors) { | ||
reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source)); | ||
} | ||
return Ternary.False; | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* A type is 'weak' if it is an object type with at least one optional property | ||
* and no required properties, call/construct signatures or index signatures | ||
*/ | ||
function isWeakType(type: Type) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: could you rename as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
const props = getPropertiesOfType(type); | ||
return type.flags & TypeFlags.Object && | ||
props.length > 0 && | ||
every(props, p => !!(p.flags & SymbolFlags.Optional)) && | ||
!getSignaturesOfType(type, SignatureKind.Call).length && | ||
!getSignaturesOfType(type, SignatureKind.Construct).length && | ||
!getIndexTypeOfType(type, IndexKind.String) && | ||
!getIndexTypeOfType(type, IndexKind.Number); | ||
} | ||
|
||
function propertiesIdenticalTo(source: Type, target: Type): Ternary { | ||
if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { | ||
return Ternary.False; | ||
|
@@ -14121,8 +14168,10 @@ namespace ts { | |
function isKnownProperty(targetType: Type, name: string, isComparingJsxAttributes: boolean): boolean { | ||
if (targetType.flags & TypeFlags.Object) { | ||
const resolved = resolveStructuredTypeMembers(<ObjectType>targetType); | ||
if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) || | ||
getPropertyOfType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) { | ||
if (resolved.stringIndexInfo || | ||
resolved.numberIndexInfo && isNumericLiteralName(name) || | ||
getPropertyOfType(targetType, name) || | ||
isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) { | ||
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. | ||
return true; | ||
} | ||
|
@@ -22671,12 +22720,12 @@ namespace ts { | |
return symbols; | ||
} | ||
else if (symbol.flags & SymbolFlags.Transient) { | ||
if ((symbol as SymbolLinks).leftSpread) { | ||
const links = symbol as SymbolLinks; | ||
return [...getRootSymbols(links.leftSpread), ...getRootSymbols(links.rightSpread)]; | ||
const transient = symbol as TransientSymbol; | ||
if (transient.leftSpread) { | ||
return [...getRootSymbols(transient.leftSpread), ...getRootSymbols(transient.rightSpread)]; | ||
} | ||
if ((symbol as SymbolLinks).syntheticOrigin) { | ||
return getRootSymbols((symbol as SymbolLinks).syntheticOrigin); | ||
if (transient.syntheticOrigin) { | ||
return getRootSymbols(transient.syntheticOrigin); | ||
} | ||
|
||
let target: Symbol; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
talk offline :: this should be renamed 🌵