Skip to content

Bug with type argument inference if it has default value = undefined and actual value is object with non-arrow function #62995

Description

There is a difference in how TypeScript infers the type automatically based on type of the argument we pass in:

  • how the value is declared (inline or by reference)
  • whether the value is an inline object with non-arrow function

The error is reproduced if generic parameter has default value undefined (this is mandatory in my case.)

🔎 Search Terms

  • "generics in constructor"
  • "type argument inference undefined"

🕗 Version & Regression Information

  • This is the behavior in every version I tried from 3.9.7 to 5.9.3

⏯ Playground Link

💻 Code

This issue with function:

function make<T, S=T, D=undefined>(value: T, options: NewOptions<T, S, D>) {
  return {
    v: value,
    data: options?.data,
    sourceValue: options?.sourceValue ?? value,
    finaleValue: options?.finaleValue,
  };
}

export type NewOptions<T, S, D> = {
  finaleValue?: T,
  sourceValue?: S,
  data: D,
};

const instance_withError = make(0, {
   // Error here: `Type '{ test(): number; }' is not assignable to type 'undefined'.(2322)`?
    data: {
        test() {
            return 123;
        },
    },
});

const data = {
    test() {
        return 123;
    },
};
// no error here
const instance1 = make(0, {
    // data is not inline object, BUT object with non-arrow function
    data,
    sourceValue: 'test',
    finaleValue: 1,
});

// no error here
const instance2 = make(0, {
    // data is inline object, BUT object with arrow function
    data: {
         test: () => {
            return 123;
        },
    },
    sourceValue: 1,
    finaleValue: 2,
});
const sV1 = instance2.sourceValue;
//    ^?
const v1 = instance2.v;
//    ^?


const sV2 = instance1.sourceValue;
//    ^?
const v2 = instance1.v;
//    ^?


// Should be `{ test(): number; }`
const data_invalid = instance_withError.data;
//    ^?
// Should be `{ test(): number; }`
const data1 = instance1.data;
//    ^?
// Should be `{ test: () => number; }`
const data2 = instance2.data;
//    ^?

🙁 Actual behavior

  1. An error occurs:
    Type '{ test(): number; }' is not assignable to type 'undefined'.(2322)
  2. data type is undefined

🙂 Expected behavior

  1. No error occurs
  2. data type is { test(): number; }

Activity

  1. nmain commented on Jan 16, 2026

    @nmain

    There's a lot of extra fluff in that repro, isn't it just this?

    Playground

    declare function make<D=undefined>(options: D): any;
    
    // cant infer
    make({
        test() {
            return 123;
        },
    });
    
    // can explictly call
    make<{ test(): number }>({
        test() {
            return 123;
        },
    })
    
    // can infer with arrow function
    make({
        test: () => {
            return 123;
        },
    });
  2. MartinJohns commented on Jan 16, 2026

    @MartinJohns
    Contributor

    Likely a duplicate of #47599.

  3. termi commented on Jan 16, 2026

    @termi
    Author

    There's a lot of extra fluff in that repro, isn't it just this?

    The reason why my example has 3 generic parameters

    declare function make<T, S=T, D=undefined>(value: T, options: NewOptions<T, S, D>);

    is to show that D=undefined default value is necessary here and cannot be removed.

    Is you case

    declare function make<D=undefined>(options: D): any;

    we can just remove =undefined and all will work fine.

  4. termi commented on Jan 16, 2026

    @termi
    Author

    Likely a duplicate of #47599.

    It's related, but not duplicate.
    In my case, adding =undefined in generic parameter causes a problem.

  5. Andarist commented on Jan 16, 2026

    @Andarist
    Contributor

    This is already fixed by #62243 . You can try out nightly build.

  6. RyanCavanaugh commented on Jan 16, 2026

    @RyanCavanaugh
    Member

    Confirmed in nightly playground

  7. hesprs commented on Jun 22, 2026

    @hesprs

    I don't consider it is fixed. Exact situation here, errors still. Tried both on TS 6.0.3 and latest TSGO.

    Simplified package code:

    // ... (A ton of generics gymnastics)
    
    type Context<
    	M extends ReadonlyArray<ModuleConstructor<General>>,
    	K extends Keys<InstanceEach<M>[number]>,
    	Pr extends object = {},
    > = MergeResult<M, K, Pr> & {
    	__assign__: (obj: Partial<MergeResult<M, K, Pr>>) => Context<M, K, Pr>;
    };
    
    declare function createContext<
    	M extends ReadonlyArray<ModuleConstructor<Context<M, K, Pr>>>,
    	K extends Keys<InstanceEach<M>[number]>,
    	Pr extends object = {},
    >(
    	classes: M,
    	options: {
    		preMerge?: Pr;
    		mergeKeys: ReadonlyArray<K>;
    	},
    ): Context<M, K, Pr>;

    Test code (simplified):

    // Works (arrow function)
    const context = createContext(allModules, {
    	mergeKeys: ['settings', 'root', 'events', 'i18n'],
    	preMerge: {
    		registerEvent: (ref: EventRef) => this.registerEvent(ref),
    	},
    }).__assign__({ settings: this.settings });
    
    // Works (hoisting)
    const preMerge = {
    	registerEvent: this.registerEvent.bind(this),
    };
    const context = createContext(allModules, {
    	mergeKeys: ['settings', 'root', 'events', 'i18n'],
    	preMerge,
    }).__assign__({ settings: this.settings });
    
    // Fails, failure message shows that `Pr` is degraded to `{}` instead of exact `preMerge` type
    const context = createContext(allModules, {
    	mergeKeys: ['settings', 'root', 'events', 'i18n'],
    	preMerge: {
    		registerEvent: this.registerEvent.bind(this),
    	},
    }).__assign__({ settings: this.settings });

    Since this is a closed issue, do I need to open a new one, or report in #47599, or do nothing?

  8. RyanCavanaugh commented on Jun 24, 2026

    @RyanCavanaugh
    Member

    Hēsperus (@hesprs) please log a new issue, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions