Skip to content

Commit c09cace

Browse files
committed
WIP: Implement type-checking for signal inputs
New implementation of type-checking for signal inputs. Much simpler, and without causing signficant type check block changes that would require more changes to the language service completion logic / or rewrites of more than 50+ handwritten code output tests for type check blocks. See previous commits for other explored solutions.
1 parent 6840f93 commit c09cace

File tree

5 files changed

+49
-10
lines changed

5 files changed

+49
-10
lines changed

packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,8 @@ class TcbDirectiveInputsOp extends TcbOp {
754754
dirId = this.scope.resolve(this.node, this.dir);
755755
}
756756

757+
// TODO(signals): signal input with a restricted modifier
758+
757759
const id = this.tcb.allocateId();
758760
const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
759761
if (!ts.isTypeReferenceNode(dirTypeRef)) {
@@ -771,14 +773,42 @@ class TcbDirectiveInputsOp extends TcbOp {
771773
dirId = this.scope.resolve(this.node, this.dir);
772774
}
773775

774-
// To get errors assign directly to the fields on the instance, using property access
775-
// when possible. String literal fields may not be valid JS identifiers so we use
776-
// literal element access instead for those cases.
777-
target = this.dir.stringLiteralInputFields.has(fieldName) ?
778-
ts.factory.createElementAccessExpression(
779-
dirId, ts.factory.createStringLiteral(fieldName)) :
780-
ts.factory.createPropertyAccessExpression(
781-
dirId, ts.factory.createIdentifier(fieldName));
776+
if (this.dir.isSignal) {
777+
const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
778+
if (!ts.isTypeReferenceNode(dirTypeRef)) {
779+
throw new Error(
780+
`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`);
781+
}
782+
783+
const getSignalWriteType = this.tcb.env.referenceExternalType(
784+
R3Identifiers.getInputSignalWriteType.moduleName,
785+
R3Identifiers.getInputSignalWriteType.name);
786+
if (!ts.isTypeReferenceNode(getSignalWriteType)) {
787+
throw new Error(`Expected TypeReferenceNode from reference to ${
788+
R3Identifiers.getInputSignalWriteType.name}`);
789+
}
790+
791+
const inputFieldType = ts.factory.createIndexedAccessTypeNode(
792+
ts.factory.createTypeQueryNode(dirId as ts.Identifier),
793+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(fieldName)));
794+
795+
const inputWriteTypeId = this.tcb.allocateId();
796+
const inputWriteTypeSt = tsDeclareVariable(
797+
inputWriteTypeId,
798+
ts.factory.createTypeReferenceNode(getSignalWriteType.typeName, [inputFieldType]));
799+
this.scope.addStatement(inputWriteTypeSt);
800+
801+
target = inputWriteTypeId;
802+
} else {
803+
// To get errors assign directly to the fields on the instance, using property access
804+
// when possible. String literal fields may not be valid JS identifiers so we use
805+
// literal element access instead for those cases.
806+
target = this.dir.stringLiteralInputFields.has(fieldName) ?
807+
ts.factory.createElementAccessExpression(
808+
dirId, ts.factory.createStringLiteral(fieldName)) :
809+
ts.factory.createPropertyAccessExpression(
810+
dirId, ts.factory.createIdentifier(fieldName));
811+
}
782812
}
783813

784814
if (attr.attribute.keySpan !== undefined) {

packages/compiler/src/render3/r3_identifiers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,8 @@ export class Identifiers {
391391
o.ExternalReference = {name: 'ɵɵtrustConstantResourceUrl', moduleName: CORE};
392392
static validateIframeAttribute:
393393
o.ExternalReference = {name: 'ɵɵvalidateIframeAttribute', moduleName: CORE};
394+
395+
396+
// type-checking
397+
static getInputSignalWriteType = {name: 'ɵɵGetInputSignalWriteType', moduleName: CORE};
394398
}

packages/core/src/core_reactivity_export_internal.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export {
2020
CreateSignalOptions,
2121
signal,
2222
WritableSignal,
23-
ɵɵtoWritableSignal
2423
} from './render3/reactivity/signal';
2524
export {
2625
untracked,
@@ -38,6 +37,6 @@ export {
3837
assertNotInReactiveContext,
3938
} from './render3/reactivity/asserts';
4039
export {input} from './render3/reactivity/input';
41-
export {InputSignal} from './render3/reactivity/input_signal';
40+
export {InputSignal, ɵɵGetInputSignalWriteType} from './render3/reactivity/input_signal';
4241
export {ModelSignal} from './render3/reactivity/model_signal';
4342
// clang-format on

packages/core/src/render3/reactivity/input_signal.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export type InputSignal<ReadT, WriteT> = Signal<ReadT>&{
2424
[BRAND_WRITE_TYPE]: WriteT;
2525
};
2626

27+
// TODO(signals)
28+
export type ɵɵGetInputSignalWriteType<T> = T extends InputSignal<any, infer X>? X : never;
29+
2730
export interface InputSignalNode<ReadT, WriteT> extends ComputedNode<ReadT> {
2831
/**
2932
* Whether the input signal is initialized. If not, accessing the node results in an error.

packages/core/test/render3/jit_environment_spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const INTERFACE_EXCEPTIONS = new Set<string>([
2121
'ɵɵPipeDeclaration',
2222
'ɵɵFactoryDeclaration',
2323
'ModuleWithProviders',
24+
25+
// Type-only needed for type checking.
26+
'ɵɵGetInputSignalWriteType',
2427
]);
2528

2629
/**

0 commit comments

Comments
 (0)