diff --git a/src/com/google/javascript/jscomp/TypeInference.java b/src/com/google/javascript/jscomp/TypeInference.java index 1204e9854b0..77021699e3e 100644 --- a/src/com/google/javascript/jscomp/TypeInference.java +++ b/src/com/google/javascript/jscomp/TypeInference.java @@ -1636,8 +1636,14 @@ private void updateBind(Node n) { } /** - * For functions with function parameters, type inference will set the type of a function literal - * argument from the function parameter type. + * Performs a limited back-inference on function arguments based on the expected parameter types. + * + *

Currently this only does back-inference in two cases: it infers the type of function literal + * arguments and adds inferred properties to inferred object-typed arguments. + * + *

For example: if someone calls `Promise.prototype.then` with `(result) => ...` then + * we infer that the type of the arrow function is `function(string): ?`, and inside the arrow + * function body we know that `result` is a string. */ private void updateTypeOfArguments(Node n, FunctionType fnType) { checkState(NodeUtil.isInvocation(n), n); @@ -1690,16 +1696,23 @@ private void updateTypeOfArguments(Node n, FunctionType fnType) { && iArgumentType.isFunctionType()) { FunctionType argFnType = iArgumentType.toMaybeFunctionType(); JSDocInfo argJsdoc = iArgument.getJSDocInfo(); - boolean declared = argJsdoc != null && argJsdoc.containsDeclaration(); + // Treat the parameter & return types of the function as 'declared' if the function has + // JSDoc with type annotations, or a parameter has inline JSDoc. + // Note that this does not distinguish between cases where all parameters have JSDoc vs + // only one parameter has JSDoc. + boolean declared = + (argJsdoc != null && argJsdoc.containsDeclaration()) + || NodeUtil.functionHasInlineJsdocs(iArgument); iArgument.setJSType(matchFunction(restrictedParameter, argFnType, declared)); } } } /** - * Take the current function type, and try to match the expected function - * type. This is a form of backwards-inference, like record-type constraint - * matching. + * Take the current function type, and try to match the expected function type. This is a form of + * backwards-inference, like record-type constraint matching. + * + * @param declared Whether the given function type is user-provided as opposed to inferred */ private FunctionType matchFunction( FunctionType expectedType, FunctionType currentType, boolean declared) { diff --git a/test/com/google/javascript/jscomp/TypeCheckTest.java b/test/com/google/javascript/jscomp/TypeCheckTest.java index 879b60583ac..b61932b415c 100644 --- a/test/com/google/javascript/jscomp/TypeCheckTest.java +++ b/test/com/google/javascript/jscomp/TypeCheckTest.java @@ -7715,13 +7715,50 @@ public void testInferredParam8() { } @Test - public void testInferredParam9() { - // TODO(b/77731069) This should warn "parameter 1 of f does not match formal parameter" + public void testFunctionLiteralParamWithInlineJSDocNotInferred() { + testTypes( + lines( + "/** @param {function(string)} x */", + "function f(x) {}", + "f(function(/** number */ x) {});"), + lines( + "actual parameter 1 of f does not match formal parameter", + "found : function(number): undefined", + "required: function(string): ?")); + } + + @Test + public void testFunctionLiteralParamWithInlineJSDocNotInferredWithTwoParams() { + testTypes( + lines( + "/** @param {function(string, number)} x */", + "function f(x) {}", + "f((/** string */ str, num) => {", + // TODO(b/123583824): this should be a type mismatch warning. + // found : number + // expected: string + // but the JSDoc on `str` blocks inference of `num`. + " const /** string */ newStr = num;", + "});")); + } + + @Test + public void testJSDocOnNoParensArrowFnParameterIsIgnored() { + // This case is to document a potential bit of confusion: what happens when writing + // inline-like JSDoc on a 'naked' arrow function parameter (no parentheses) + // The actual behavior is that the JSDoc does nothing: this is equivalent to writing + // /** number */ function(x) { ... testTypes( lines( "/** @param {function(string)} callback */", "function foo(callback) {}", - "foo(function(/** number */ x) {});")); + // The `/** number */` JSDoc is attached to the entire arrow function, not the + // parameter `x` + "foo(/** number */ x => { var /** number */ y = x; });"), + lines( + "initializing variable", + "found : string", // type of "x" is inferred to be string + "required: number")); } @Test