Handle generics in @type declaration for functions

24 views
Skip to first unread message

Ned Lud

unread,
Mar 4, 2023, 11:00:38 AM3/4/23
to Closure Compiler Discuss
Let me explain with example right away. So there's a function in externs: 

/**
* @param {INPUTS} inputs
* @param {string} testing
* @return {com.webcircuits.Serialised}
* @template INPUTS
* @this {com.webcircuits.IDriverFront}
*/
$$com.webcircuits.IDriverFront._serInputs = function(inputs,testing) {}

I want to use it to annotate the function in source code: 

/** @type {com.webcircuits.IDriverFront._serInputs<com.webcircuits.IDriverFront.Inputs>} */
export default function _serInputs(inputs,testing){
test({inputs,testing})
test({inputs:this})
const{
asIDriverFront:{inputsPQs:inputsPQs},
}=this
const _inputs=ser(inputs,inputsPQs)
return _inputs
}

/**
* @param {string} s
*/
function test(s){}

Now, if I just tried to set @type {com.webcircuits.IDriverFront._serInputs}, the compiler would say that the type does not exist which is fair enough since the function is not defined as a type. That''s why there's a $$ before it, and then I do

/** @typedef {typeof $$com.webcircuits.IDriverFront._serInputs} */
com.webcircuits.IDriverFront._serInputs

in externs, so that it basically becomes a type. It's pretty much all good except that checking for correctness of this approach with the test call, I see:

src/class/DriverFront/methods/ser-inputs/setInputs.js:10:6: WARNING - [JSC_TYPE_MISMATCH] actual parameter 1 of test$$module$src$class$DriverFront$methods$ser_inputs$setInputs does not match formal parameter
found   : {
  inputs: INPUTS,
  testing: string
}
required: string
  10|  test({inputs,testing})
            ^^^^^^^^^^^^^^^^


src/class/DriverFront/methods/ser-inputs/setInputs.js:11:6: WARNING - [JSC_TYPE_MISMATCH] actual parameter 1 of test$$module$src$class$DriverFront$methods$ser_inputs$setInputs does not match formal parameter
found   : {inputs: com.webcircuits.IDriverFront}
required: string
  11|  test({inputs:this})
            ^^^^^^^^^^^^^


So it worked as intended because the this type is resolved as per original function annotation as well as testing variable type is a string which is right, but the inputs are left as INPUTS so the compiler didn't pick up those generics. Could you please make it possible to use generics via @type {function-ref} as I've just described? The same thing applies if the function is defined in the source code using the same @typedef {typeof $$fn} method in source not just externs.

Btw, if export default is removed, this @type trick does not work at all but I'm not bothered by that. I just need the generics to be picked up by the compiler, if somebody could have a look at why generics are not passed through that would be amazing <3.

Thanks in advance!
NL

Christopher Allen

unread,
Mar 16, 2023, 9:05:48 AM3/16/23
to Closure Compiler Discuss
Ned,
 
/**
* @param {INPUTS} inputs
* @param {string} testing
* @return {com.webcircuits.Serialised}
* @template INPUTS
* @this {com.webcircuits.IDriverFront}
*/
$$com.webcircuits.IDriverFront._serInputs = function(inputs,testing) {}


As an aside: I can't help but notice that this type declaration violates the golden rule of generics, insofar as the INPUT parameter only appears once.  Could it not have been written @param {*} inputs ?

It's pretty much all good except that checking for correctness of this approach with the test call, I see:

  inputs: INPUTS,

So it worked as intended because the this type is resolved as per original function annotation as well as testing variable type is a string which is right, but the inputs are left as INPUTS so the compiler didn't pick up those generics.

Interesting.  I also would have expected to be able to instantiate the template in this way and have the supplied type visible inside the function body, but it seems that you can't even use @typedef to instantiate a generic function type:

/**
 * @template T
 * @param {T} input
 */
const TtakerExample = function(input) {};

/** @typedef {typeof TtakerExample} */
var Ttaker;

const /** Ttaker<number> */ numberTaker = function(input) {};

const /** null */ _ = numberTaker;  // found: function(T): undefined

 

/**
 * @template T
 * @param {T} input
 */
const TtakerExample = function(input) {};

/** @typedef {typeof TtakerExample} */
var Ttaker;

/** @param {number} input */
function numberTaker(input) {};

const /** Ttaker<number> */ f = numberTaker;  // Legal.

const /** null */ _x = numberTaker;  // found: function(number): undefined
const /** null */ _y = f;  // found: function(T): undefined

 
Most curious.

I've filed bug #4070 about this.

Btw, if export default is removed, this @type trick does not work at all but I'm not bothered by that.

Can you explain what you mean by "the @type trick does not work at all"?  In particular, I'm not quite sure what the "trick" is and what 'not working' means.  I am doubtless naïve about Closure Compiler (and particularly its internals), but everything here looks fairly straight forward* and seems like it should work the way you expect it to.

* At least once I noticed that your @typedef included a typeof.  Before that I was generally a bit confused about what you were doing!


Christopher

Reply all
Reply to author
Forward
0 new messages