-
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
Quick fix for no-implicit-any errors to add explicit type annotation #14786
Conversation
I am delighted by this. Thank you @mhegazy! |
src/compiler/checker.ts
Outdated
@@ -205,6 +205,24 @@ namespace ts { | |||
return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); | |||
}, | |||
getApparentType, | |||
|
|||
getUnionType, |
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.
Nit: Maybe makeUnionType
or createUnionType
? This works too though.
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code: | ||
return getCodeActionForVariableDeclaration(<PropertyDeclaration | PropertySignature | VariableDeclaration>token.parent); | ||
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: | ||
return getCodeActionForVariableUsage(<Identifier>token); |
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.
I feel like there should be assertions when you cast based on the error code you're matching on.
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.
We asserted a few lines earlier that it has to be an identifier, DotDotDotToken, PublicKeyword, ProtectedKeyword, or ReadonlyKeyword. since this is a variable declaration error, we know it can not be anything that is not an identifier (i.e. binding pattern).
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: | ||
return getCodeActionForVariableUsage(<Identifier>token); | ||
|
||
// Paramtert declarations |
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.
Parameter
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.
Done
return typeString; | ||
} | ||
|
||
function getParameterIndexInList(parameter: ParameterDeclaration, list: NodeArray<ParameterDeclaration>) { |
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.
Isn't this just indexOf
?
for (const child of node.getChildren(sourcefile)) { | ||
if (child.kind === kind) return child; | ||
} | ||
} |
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.
explicit return undefined
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.
done.
return -1; | ||
} | ||
|
||
function getFirstChildOfKind(node: Node, sourcefile: SourceFile, kind: SyntaxKind) { |
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.
There's a findChildOfKind
which is nice and slow and allocates the lamdba you were trying to avoid here.
|
||
// Property declarations | ||
case Diagnostics.Member_0_implicitly_has_an_1_type.code: | ||
return getCodeActionForVariableDeclaration(<VariableDeclaration>token.parent); |
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.
Is there ever a time that you won't have a symbol that you can't call this directly? What about in the case of a computed property?
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.
We filter these earlier on. it can only be identifier for a var declarations
Conflicts: src/compiler/checker.ts src/compiler/diagnosticMessages.json src/compiler/types.ts src/services/tsconfig.json
getUndefinedType: () => undefinedType, | ||
getNullType: () => nullType, | ||
getESSymbolType: () => esSymbolType, | ||
getNeverType: () => neverType, |
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.
As a concern, we have multiple never
types internally. I feel somewhat uneasy about exposing it.
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.
made them all them internal for now.
Add new lines to the end of files? |
@DanielRosenwasser any more comments? |
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.
A few questions about inference, plus I noticed some typos.
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code: | ||
return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction) : undefined; | ||
|
||
// Property declarations |
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.
This looks redundant with the very first case of this switch.
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.
thanks. removed.
errorCodes: [ | ||
// Variable declarations | ||
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code, | ||
Diagnostics.Variable_0_implicitly_has_an_1_type.code, |
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.
this error maps to getCodeActionForVariableUsage
, so I think it is actually should be commented with // variable uses
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.
sure :)
return undefined; | ||
} | ||
|
||
const types = inferTypeForParametersFromUsage(containingFunction) || |
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.
so it's either from usage from calls or from intra-function usages, but not both? Why have inferences from call sites win?
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.
If we do the function body first we are guranteed to manifacture something that the makes the funciton happy, but it usually will be too general. giving usage precedence allows us to get more specific types.. e.g.:
function length(a) { return a.length; }
length([1,2,3]);
inferring from function body will give a
a type { length: any}
. which is accurate, but rarely what the user would write manually.
inferring from the call site gives us number[]
which is more like it..
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.
Perhaps you should infer <T extends { length }>(a: T) => ...
instead. In combination with return type inference, this should yield <T extends { length }>(a: T) => T["length"]
, which will work correctly when applied to arrays (or indeed anything with a length
property).
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.
I tried that.. the types get unwieldy very fast.. and you have this structural type that has length
, push
, splice
, with an element that is has kind
, instead of just Node[]
. the call sites provided the more user-identifiable name.. though the first one is more correct.
const types = inferTypeForParametersFromUsage(containingFunction) || | ||
map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name)); | ||
|
||
if (types) { |
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.
it would be easier to read both nested blocks as early returns (if (!types) return undefined
and if (!textChanges) return undefined
.
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.
done.
|
||
if (types) { | ||
const textChanges = reduceLeft(containingFunction.parameters, (memo, parameter, index) => { | ||
if (types[index] && !parameter.type && !parameter.initializer) { |
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.
containingFunction.parameters.length === types.length
, right?
If so, I don't think we need the efficiency of reduce
and lazy initialisation of memo
. It would be easier to read as a zip followed by filtering out undefined
:
zipWith(containingFunction.parameters, types, (param, type) => ...).filter(change => change !== undefined)
(zipWith is in core.ts so it might need to be moved into the public API?)
} | ||
|
||
if (usageContext.callContexts) { | ||
for (const callConext of usageContext.callContexts) { |
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.
typo:callContext
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.
done
return checker.createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, callContext.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); | ||
} | ||
|
||
function inferTypeFromContext(node: Expression, checker: TypeChecker, usageContext: UsageContext): void { |
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.
Can you move this before getTypeFromUsageContext since it's used before?
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.
Ok, moved get* after infer*
return; | ||
} | ||
else { | ||
const indextType = checker.getTypeAtLocation(parent); |
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.
probably is a typo for indexType
?
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.
done
function getSignatureFromCallContext(callContext: CallContext, checker: TypeChecker): Signature { | ||
const parameters: Symbol[] = []; | ||
for (let i = 0; i < callContext.argumentTypes.length; i++) { | ||
const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`p${i}`)); |
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.
I would call this arg${i}
to match our other names for unnamed parameters.
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.
done
else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) { | ||
return checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String).callContexts, /*isRestParameter*/ false, checker)); | ||
} | ||
else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.numberIndexContext || usageContext.stringIndexContext) { |
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.
what happens if you combine object types made up of the synthetic properties (et al) with the candidate types? Are the candidate types strictly better than the synthetic object type?
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.
I would think so.. a user-defined type seems more like what you would put on a function, e.g. Node
or Symbol
, and not {kind: SyntaxKind}
or {flags: SymbolFlags}
to use an example of our code base.
Add a new quick fix for no-implicit-any errors for variables, parameters, and members. The fix tries to "infer" the type from assignments to the symbol in question, and serializes the type in an explicit type annotation addressing the no-implicit-any error.