Skip to content

Commit 7049af5

Browse files
authored
fix(58166): Class parameter property with initializer before required property emits non-nullable parameter for declaration emit (microsoft#58177)
1 parent f046728 commit 7049af5

10 files changed

+85
-17
lines changed

src/compiler/checker.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -8316,7 +8316,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
83168316
* @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed
83178317
*/
83188318
function serializeTypeForDeclaration(context: NodeBuilderContext, declaration: Declaration | undefined, type: Type, symbol: Symbol) {
8319-
const addUndefinedForParameter = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration);
8319+
const addUndefinedForParameter = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration, context.enclosingDeclaration);
83208320
const enclosingDeclaration = context.enclosingDeclaration;
83218321
const restoreFlags = saveRestoreFlags(context);
83228322
if (declaration && hasInferredType(declaration) && !(context.internalFlags & InternalNodeBuilderFlags.NoSyntacticPrinter)) {
@@ -49591,16 +49591,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4959149591
const type = getTypeFromTypeNode(typeNode);
4959249592
return containsUndefinedType(type);
4959349593
}
49594-
function requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag) {
49595-
return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter);
49594+
49595+
function requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag, enclosingDeclaration: Node | undefined) {
49596+
return (isRequiredInitializedParameter(parameter, enclosingDeclaration) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter);
4959649597
}
4959749598

49598-
function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean {
49599-
return !!strictNullChecks &&
49600-
!isOptionalParameter(parameter) &&
49601-
!isJSDocParameterTag(parameter) &&
49602-
!!parameter.initializer &&
49603-
!hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier);
49599+
function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag, enclosingDeclaration: Node | undefined): boolean {
49600+
if (!strictNullChecks || isOptionalParameter(parameter) || isJSDocParameterTag(parameter) || !parameter.initializer) {
49601+
return false;
49602+
}
49603+
if (hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier)) {
49604+
return !!enclosingDeclaration && isFunctionLikeDeclaration(enclosingDeclaration);
49605+
}
49606+
return true;
4960449607
}
4960549608

4960649609
function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration | JSDocParameterTag) {

src/compiler/expressionToTypeNode.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export function createSyntacticTypeNodeBuilder(options: CompilerOptions, resolve
178178
return typeFromAccessor(parent, context);
179179
}
180180
const declaredType = getEffectiveTypeAnnotationNode(node);
181-
const addUndefined = resolver.requiresAddingImplicitUndefined(node);
181+
const addUndefined = resolver.requiresAddingImplicitUndefined(node, context.enclosingDeclaration);
182182
let resultType;
183183
if (declaredType) {
184184
resultType = serializeExistingTypeAnnotation(declaredType, addUndefined);

src/compiler/transformers/declarations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ export function transformDeclarations(context: TransformationContext) {
680680
// Literal const declarations will have an initializer ensured rather than a type
681681
return;
682682
}
683-
const shouldAddImplicitUndefined = node.kind === SyntaxKind.Parameter && resolver.requiresAddingImplicitUndefined(node);
683+
const shouldAddImplicitUndefined = node.kind === SyntaxKind.Parameter && resolver.requiresAddingImplicitUndefined(node, enclosingDeclaration);
684684
if (type && !shouldAddImplicitUndefined) {
685685
return visitNode(type, visitDeclarationSubtree, isTypeNode);
686686
}

src/compiler/transformers/declarations/diagnostics.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ export function createGetIsolatedDeclarationErrors(resolver: EmitResolver) {
767767
if (isSetAccessor(node.parent)) {
768768
return createAccessorTypeError(node.parent);
769769
}
770-
const addUndefined = resolver.requiresAddingImplicitUndefined(node);
770+
const addUndefined = resolver.requiresAddingImplicitUndefined(node, /*enclosingDeclaration*/ undefined);
771771
if (!addUndefined && node.initializer) {
772772
return createExpressionError(node.initializer);
773773
}

src/compiler/types.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -5293,7 +5293,7 @@ export interface TypeChecker {
52935293
/** @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken, nodesToCheck?: Node[]): Diagnostic[];
52945294
/** @internal */ getGlobalDiagnostics(): Diagnostic[];
52955295
/** @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken, forceDts?: boolean): EmitResolver;
5296-
/** @internal */ requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag): boolean;
5296+
/** @internal */ requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag, enclosingDeclaration: Node | undefined): boolean;
52975297

52985298
/** @internal */ getNodeCount(): number;
52995299
/** @internal */ getIdentifierCount(): number;
@@ -5814,7 +5814,7 @@ export interface EmitResolver {
58145814
collectLinkedAliases(node: ModuleExportName, setVisibility?: boolean): Node[] | undefined;
58155815
markLinkedReferences(node: Node): void;
58165816
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
5817-
requiresAddingImplicitUndefined(node: ParameterDeclaration): boolean;
5817+
requiresAddingImplicitUndefined(node: ParameterDeclaration, enclosingDeclaration: Node | undefined): boolean;
58185818
isExpandoFunctionDeclaration(node: FunctionDeclaration | VariableDeclaration): boolean;
58195819
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
58205820
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression | ElementAccessExpression | BinaryExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, internalFlags: InternalNodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
@@ -10378,6 +10378,6 @@ export interface SyntacticTypeNodeBuilderResolver {
1037810378
isExpandoFunctionDeclaration(name: FunctionDeclaration | VariableDeclaration): boolean;
1037910379
getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations;
1038010380
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible?: boolean): SymbolVisibilityResult;
10381-
requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag): boolean;
10381+
requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag, enclosingDeclaration: Node | undefined): boolean;
1038210382
isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean;
1038310383
}

src/services/codefixes/fixMissingTypeAnnotationOnExports.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -914,11 +914,12 @@ function withContext<T>(
914914
type = widenedType;
915915
}
916916

917-
if (isParameter(node) && typeChecker.requiresAddingImplicitUndefined(node)) {
917+
const enclosingDeclaration = findAncestor(node, isDeclaration) ?? sourceFile;
918+
if (isParameter(node) && typeChecker.requiresAddingImplicitUndefined(node, enclosingDeclaration)) {
918919
type = typeChecker.getUnionType([typeChecker.getUndefinedType(), type], UnionReduction.None);
919920
}
920921
return {
921-
typeNode: typeToTypeNode(type, findAncestor(node, isDeclaration) ?? sourceFile, getFlags(type)),
922+
typeNode: typeToTypeNode(type, enclosingDeclaration, getFlags(type)),
922923
mutatedTarget: false,
923924
};
924925

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [tests/cases/compiler/parameterPropertyInConstructor4.ts] ////
2+
3+
//// [parameterPropertyInConstructor4.ts]
4+
export class C {
5+
constructor(public a: number[] = [], b: number) {
6+
}
7+
}
8+
9+
10+
//// [parameterPropertyInConstructor4.js]
11+
"use strict";
12+
Object.defineProperty(exports, "__esModule", { value: true });
13+
exports.C = void 0;
14+
var C = /** @class */ (function () {
15+
function C(a, b) {
16+
if (a === void 0) { a = []; }
17+
this.a = a;
18+
}
19+
return C;
20+
}());
21+
exports.C = C;
22+
23+
24+
//// [parameterPropertyInConstructor4.d.ts]
25+
export declare class C {
26+
a: number[];
27+
constructor(a: number[] | undefined, b: number);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//// [tests/cases/compiler/parameterPropertyInConstructor4.ts] ////
2+
3+
=== parameterPropertyInConstructor4.ts ===
4+
export class C {
5+
>C : Symbol(C, Decl(parameterPropertyInConstructor4.ts, 0, 0))
6+
7+
constructor(public a: number[] = [], b: number) {
8+
>a : Symbol(C.a, Decl(parameterPropertyInConstructor4.ts, 1, 16))
9+
>b : Symbol(b, Decl(parameterPropertyInConstructor4.ts, 1, 40))
10+
}
11+
}
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [tests/cases/compiler/parameterPropertyInConstructor4.ts] ////
2+
3+
=== parameterPropertyInConstructor4.ts ===
4+
export class C {
5+
>C : C
6+
> : ^
7+
8+
constructor(public a: number[] = [], b: number) {
9+
>a : number[]
10+
> : ^^^^^^^^
11+
>[] : never[]
12+
> : ^^^^^^^
13+
>b : number
14+
> : ^^^^^^
15+
}
16+
}
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// @declaration: true
2+
// @strict: true
3+
4+
export class C {
5+
constructor(public a: number[] = [], b: number) {
6+
}
7+
}

0 commit comments

Comments
 (0)