Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions internal/pseudochecker/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,9 @@ func typeNodeCouldReferToUndefined(node *ast.Node) bool {
return true
case ast.KindTypePredicate: // suspect - always refers to `never` or `boolean`, depending on kind - considered possibly-`undefined` referencing for strada compat
return true
default: // all keywords (why is `undefined` not excluded???), literal types, function-y types, array/tuple types, type literals, template types, this types
case ast.KindUndefinedKeyword:
return true
default: // all other keywords, literal types, function-y types, array/tuple types, type literals, template types, this types
return false
}
}
Expand Down Expand Up @@ -626,7 +628,21 @@ func (ch *PseudoChecker) typeFromParameter(node *ast.ParameterDeclaration) *Pseu
}
declaredType := node.Type
if declaredType != nil {
return NewPseudoTypeDirect(declaredType)
result := NewPseudoTypeDirect(declaredType)
// When the parameter has an initializer and strict null checks are enabled,
// check if `| undefined` needs to be added because there are required parameters after this one.
// This mirrors the checker's getTypeOfParameter which adds optionality for initialized parameters.
if ch.strictNullChecks && node.Initializer != nil {
p := node.Parent.Parameters()
selfIdx := slices.Index(p, node.AsNode())
if selfIdx < len(p)-1 {
remainingParams := p[selfIdx+1:]
if !core.Every(remainingParams, isOptionalInitializedOrRestParameter) {
return addUndefinedIfDefinitelyRequired(result)
}
}
Comment on lines +635 to +643
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeFromParameter now does a linear slices.Index lookup plus a scan of the remaining parameter list (core.Every) to decide whether to add | undefined. When invoked for many parameters (e.g. large signatures), this can become O(n^2) work per function. Consider threading the parameter index / “has required parameter after this one” information down from the caller, or precomputing a suffix flag over the parameter list once and reusing it here.

Copilot uses AI. Check for mistakes.
}
return result
}
if node.Initializer != nil && ast.IsIdentifier(node.Name()) && !isContextuallyTyped(node.AsNode()) {
expr := ch.typeFromExpression(node.Initializer)
Expand Down Expand Up @@ -660,12 +676,21 @@ func (ch *PseudoChecker) cloneParameters(nodes *ast.NodeList) []*PseudoParameter
return nil
}
result := make([]*PseudoParameter, 0, len(nodes.Nodes))
for _, e := range nodes.Nodes {
for i, e := range nodes.Nodes {
p := e.AsParameterDeclaration()
optional := p.QuestionToken != nil
if !optional && p.Initializer != nil {
// A parameter with an initializer is optional only if all subsequent
// parameters are also optional/have initializers/are rest parameters.
// This matches the checker's isOptionalParameter semantics.
remainingParams := nodes.Nodes[i+1:]
optional = core.Every(remainingParams, isOptionalInitializedOrRestParameter)
}
Comment on lines +679 to +688
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cloneParameters computes optionality for initialized parameters by scanning all subsequent parameters (core.Every(remainingParams, ...)) for each parameter. This makes cloning parameters O(n^2) in the number of parameters. Consider computing a single suffix boolean while iterating from the end (e.g. “all following are optional/initialized/rest”), so optionality can be determined in O(1) per parameter.

Copilot uses AI. Check for mistakes.
result = append(result, NewPseudoParameter(
e.AsParameterDeclaration().DotDotDotToken != nil,
p.DotDotDotToken != nil,
e.Name(),
e.AsParameterDeclaration().QuestionToken != nil || e.AsParameterDeclaration().Initializer != nil,
ch.typeFromParameter(e.AsParameterDeclaration()),
optional,
ch.typeFromParameter(p),
))
}
return result
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//// [tests/cases/compiler/isolatedDeclarationsArrowFunctionDefaultParameter.ts] ////

//// [isolatedDeclarationsArrowFunctionDefaultParameter.ts]
export const foo = (one: string = "DEFAULT", two: string): string => {
return one + " " + two;
};

export const bar = (one: string | undefined = "DEFAULT", two: string): string => {
return one + " " + two;
};

// Arrow function where the default parameter is the last one (should be optional)
export const baz = (one: string, two: string | undefined = "DEFAULT"): string => {
return one + " " + two;
};

// Arrow function where all parameters have defaults
export const qux = (one: string = "DEFAULT", two: string = "DEFAULT"): string => {
return one + " " + two;
};


//// [isolatedDeclarationsArrowFunctionDefaultParameter.js]
export const foo = (one = "DEFAULT", two) => {
return one + " " + two;
};
export const bar = (one = "DEFAULT", two) => {
return one + " " + two;
};
// Arrow function where the default parameter is the last one (should be optional)
export const baz = (one, two = "DEFAULT") => {
return one + " " + two;
};
// Arrow function where all parameters have defaults
export const qux = (one = "DEFAULT", two = "DEFAULT") => {
return one + " " + two;
};


//// [isolatedDeclarationsArrowFunctionDefaultParameter.d.ts]
export declare const foo: (one: string | undefined, two: string) => string;
export declare const bar: (one: string | undefined, two: string) => string;
export declare const baz: (one: string, two?: string | undefined) => string;
export declare const qux: (one?: string, two?: string) => string;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//// [tests/cases/compiler/isolatedDeclarationsArrowFunctionDefaultParameter.ts] ////

=== isolatedDeclarationsArrowFunctionDefaultParameter.ts ===
export const foo = (one: string = "DEFAULT", two: string): string => {
>foo : Symbol(foo, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 0, 12))
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 0, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 0, 44))

return one + " " + two;
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 0, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 0, 44))

};

export const bar = (one: string | undefined = "DEFAULT", two: string): string => {
>bar : Symbol(bar, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 4, 12))
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 4, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 4, 56))

return one + " " + two;
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 4, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 4, 56))

};

// Arrow function where the default parameter is the last one (should be optional)
export const baz = (one: string, two: string | undefined = "DEFAULT"): string => {
>baz : Symbol(baz, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 9, 12))
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 9, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 9, 32))

return one + " " + two;
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 9, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 9, 32))

};

// Arrow function where all parameters have defaults
export const qux = (one: string = "DEFAULT", two: string = "DEFAULT"): string => {
>qux : Symbol(qux, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 14, 12))
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 14, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 14, 44))

return one + " " + two;
>one : Symbol(one, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 14, 20))
>two : Symbol(two, Decl(isolatedDeclarationsArrowFunctionDefaultParameter.ts, 14, 44))

};

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//// [tests/cases/compiler/isolatedDeclarationsArrowFunctionDefaultParameter.ts] ////

=== isolatedDeclarationsArrowFunctionDefaultParameter.ts ===
export const foo = (one: string = "DEFAULT", two: string): string => {
>foo : (one: string | undefined, two: string) => string
>(one: string = "DEFAULT", two: string): string => { return one + " " + two;} : (one: string | undefined, two: string) => string
>one : string
>"DEFAULT" : "DEFAULT"
>two : string

return one + " " + two;
>one + " " + two : string
>one + " " : string
>one : string
>" " : " "
>two : string

};

export const bar = (one: string | undefined = "DEFAULT", two: string): string => {
>bar : (one: string | undefined, two: string) => string
>(one: string | undefined = "DEFAULT", two: string): string => { return one + " " + two;} : (one: string | undefined, two: string) => string
>one : string | undefined
>"DEFAULT" : "DEFAULT"
>two : string

return one + " " + two;
>one + " " + two : string
>one + " " : string
>one : string
>" " : " "
>two : string

};

// Arrow function where the default parameter is the last one (should be optional)
export const baz = (one: string, two: string | undefined = "DEFAULT"): string => {
>baz : (one: string, two?: string | undefined) => string
>(one: string, two: string | undefined = "DEFAULT"): string => { return one + " " + two;} : (one: string, two?: string | undefined) => string
>one : string
>two : string | undefined
>"DEFAULT" : "DEFAULT"

return one + " " + two;
>one + " " + two : string
>one + " " : string
>one : string
>" " : " "
>two : string

};

// Arrow function where all parameters have defaults
export const qux = (one: string = "DEFAULT", two: string = "DEFAULT"): string => {
>qux : (one?: string, two?: string) => string
>(one: string = "DEFAULT", two: string = "DEFAULT"): string => { return one + " " + two;} : (one?: string, two?: string) => string
>one : string
>"DEFAULT" : "DEFAULT"
>two : string
>"DEFAULT" : "DEFAULT"

return one + " " + two;
>one + " " + two : string
>one + " " : string
>one : string
>" " : " "
>two : string

};

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
file2.ts(1,26): error TS9025: Declaration emit for this parameter requires implicitly adding undefined to its type. This is not supported with --isolatedDeclarations.
file2.ts(4,27): error TS9025: Declaration emit for this parameter requires implicitly adding undefined to its type. This is not supported with --isolatedDeclarations.


Expand All @@ -11,11 +10,8 @@ file2.ts(4,27): error TS9025: Declaration emit for this parameter requires impli
f = 2;
}

==== file2.ts (2 errors) ====
==== file2.ts (1 errors) ====
export function foo(p = (ip = 10, v: number): void => {}): void{
~~~~~~~
!!! error TS9025: Declaration emit for this parameter requires implicitly adding undefined to its type. This is not supported with --isolatedDeclarations.
!!! related TS9028 file2.ts:1:26: Add a type annotation to the parameter ip.
}
type T = number
export function foo2(p = (ip = 10 as T, v: number): void => {}): void{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,11 @@
+++ new.isolatedDeclarationsAddUndefined.errors.txt
@@= skipped -0, +0 lines =@@
-file2.ts(4,38): error TS9011: Parameter must have an explicit type annotation with --isolatedDeclarations.
+file2.ts(1,26): error TS9025: Declaration emit for this parameter requires implicitly adding undefined to its type. This is not supported with --isolatedDeclarations.
+file2.ts(4,27): error TS9025: Declaration emit for this parameter requires implicitly adding undefined to its type. This is not supported with --isolatedDeclarations.


==== file1.ts (0 errors) ====
@@= skipped -9, +10 lines =@@
f = 2;
}

-==== file2.ts (1 errors) ====
+==== file2.ts (2 errors) ====
export function foo(p = (ip = 10, v: number): void => {}): void{
+ ~~~~~~~
+!!! error TS9025: Declaration emit for this parameter requires implicitly adding undefined to its type. This is not supported with --isolatedDeclarations.
+!!! related TS9028 file2.ts:1:26: Add a type annotation to the parameter ip.
@@= skipped -14, +14 lines =@@
}
type T = number
export function foo2(p = (ip = 10 as T, v: number): void => {}): void{}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @declaration: true
// @isolatedDeclarations: true
// @strict: true

export const foo = (one: string = "DEFAULT", two: string): string => {
return one + " " + two;
};

export const bar = (one: string | undefined = "DEFAULT", two: string): string => {
return one + " " + two;
};

// Arrow function where the default parameter is the last one (should be optional)
export const baz = (one: string, two: string | undefined = "DEFAULT"): string => {
return one + " " + two;
};

// Arrow function where all parameters have defaults
export const qux = (one: string = "DEFAULT", two: string = "DEFAULT"): string => {
return one + " " + two;
};
Loading