You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Function interface doesn't type check return value for extra keys #12632
interfaceF{(p: {a:number}): {a: string}}// there are no type check for extra keys// but F interface has return typeletg: F=({a})=>({a: '5',b: '',})// we need to do that!, but F interface also has return typeletf: F=({a}): {a: string}=>({a: '5',b: '',})
Expected behavior:
'g' function should be detected as an error, because return type of interface F doesn't have key 'b', just haves 'a' key
functionfun1(params: {a: number,b: string}) : {c: number,d: string}{return{c: 0,d: '',}}// this is detected by compilerfunctionfun2(params: {a: number,b: string}) : {c: number,d: string}{return{c: 0,d: '',x: 0}}// I want to be able to do (to be DRY):interfaceF{(params: {a: number,b: string}): {c: number,d: string}}constfun1_v2: F=function(params){return{c: 0,d: '',}}// this function has the same type as fun2, but is not detected by compilerconstfun2_v2: F=function(params){return{c: 0,d: '',x: 0}}
is there any reason you're trying to prohibit excess properties here?
I'd like give an additional use case for this feature.
I've an API that is similar to webpack-block. So the basic idea is that we want to create a configuration object with a lot of fields, and we'd like to use some form of composition to encapsulate similar patterns in that object.
To keep it simple let's say that to produce the following configuration object:
and have entryPoint and setOuput utilities return a PartialConfig, and createConfig take a Array<PartialConfig> as an input. Then if there is a typo and setOutput define a fileName instead of filename the compiler will catch that.
Now the problem is that we don't want entryPoint and setOutput utilities to return PartialConfig but instead to return a partial config transformer, ie a function of type:
I only changed freshness checking of nested object literals when contextually typed by a union. I think that’s the only recent change, so I don’t think it’s fixed. I’d have to test it to know for sure, though.
enumEConstants{FOO='foo',BAR='bar',}interfaceIState{id: string,name: string,}functionconverter<EKeysextendsstring,T>(functionMap: {[KeyinEKeys] : (objectIn: T)=>T}){returnfunctionMap}letresult=converter<EConstants,IState>({[EConstants.FOO]: (objectIn)=>({
...objectIn,extraProp: 'extra'// <-- this isn't detected as invalid!// even though the intellisense for converter // can see that this function should return an IState type}),[EConstants.BAR]: (objectIn)=>({
...objectIn})})
Object literals lose their freshness when widened, and we widen return types. I tried a couple of experiments:
Retain flags when widening. The compiler failed to build with this setting. Numerous casts are no longer allowed.
Retain flags when widening return types only. This caused freshness to persist on the function return type, which caused a couple of bugs like:
functionf(name: string,age: number){return{ name, age }}letjustName: {name: string}=f("Bob",42)
Now you get an error that age is an excess property because the fresh object literal { name, age } gets persisted as the return type of f and of course { name, age } is excess when assigned to just { name: string }.
To make this work, we would need to think about all the places where freshness would need to be removed: function declarations would be one, assignments another. I think there are others.
Activity
DanielRosenwasser commentedon Dec 3, 2016
I'm not necessarily sure at which point in the process the object literal loses its freshness.
Carlos Galarza (@carloslfu) is there any reason you're trying to prohibit excess properties here?
carloslfu commentedon Dec 3, 2016
Suppose this scenario:
playground link
mquandalle commentedon Jun 27, 2017
As Daniel Rosenwasser (@DanielRosenwasser) asked
I'd like give an additional use case for this feature.
I've an API that is similar to webpack-block. So the basic idea is that we want to create a configuration object with a lot of fields, and we'd like to use some form of composition to encapsulate similar patterns in that object.
To keep it simple let's say that to produce the following configuration object:
we want to write the following code:
and have the type checker ensure that the config is valid:
This composition design could almost already work as shown in #12769 (comment). We could indeed create a partial config type:
and have
entryPointandsetOupututilities return aPartialConfig, andcreateConfigtake aArray<PartialConfig>as an input. Then if there is a typo andsetOutputdefine afileNameinstead offilenamethe compiler will catch that.Now the problem is that we don't want
entryPointandsetOutpututilities to returnPartialConfigbut instead to return a partial config transformer, ie a function of type:that way utilities functions could execute more complex transformations on the configuration object, and not be limited to inserting additional keys.
However now if we introduce a typo again (
setOutputusesfileNameinstead offilename), it won't be caught by the compiler anymore.The minimal problem is exposed in this snippet:
Note that if you had an explicit type annotation for the callback return type, the typechecker is working as expected:
but I would expect that this annotation would be inferred from the
barWithCallbacksignature.RyanCavanaugh commentedon Feb 5, 2018
Nathan Shively-Sanders (@sandersn) is this fixed?
sandersn commentedon Feb 5, 2018
I only changed freshness checking of nested object literals when contextually typed by a union. I think that’s the only recent change, so I don’t think it’s fixed. I’d have to test it to know for sure, though.
WhaleMade commentedon Mar 23, 2018
So.... any timeline on when this will be fixed?
TypeScript Version: 2.7.2
our use case:
Playground Link
sandersn commentedon Mar 23, 2018
Object literals lose their freshness when widened, and we widen return types. I tried a couple of experiments:
Now you get an error that
ageis an excess property because the fresh object literal{ name, age }gets persisted as the return type offand of course{ name, age }is excess when assigned to just{ name: string }.To make this work, we would need to think about all the places where freshness would need to be removed: function declarations would be one, assignments another. I think there are others.
RyanCavanaugh commentedon Aug 21, 2019
Going to track at #241