Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2 Signal inputs #53571

Closed
wants to merge 25 commits into from
Closed

#2 Signal inputs #53571

wants to merge 25 commits into from

Conversation

devversion
Copy link
Member

Please refer to #53521 for information on signal inputs. This is the second PR towards completing input signals.

See individual commits.

@angular-robot angular-robot bot added the area: build & ci Related the build and CI infrastructure of the project label Dec 14, 2023
@ngbot ngbot bot added this to the Backlog milestone Dec 14, 2023
@devversion devversion added action: review The PR is still awaiting reviews from at least one requested reviewer area: core Issues related to the framework runtime area: compiler Issues related to `ngc`, Angular's template compiler target: minor This PR is targeted for the next minor release cross-cutting: signals labels Dec 14, 2023
@devversion devversion marked this pull request as ready for review December 14, 2023 16:36
Copy link
Member

@JoostK JoostK left a comment

Choose a reason for hiding this comment

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

Nice work! 👏

packages/core/src/authoring/input.ts Outdated Show resolved Hide resolved
packages/core/src/authoring/input_signal.ts Outdated Show resolved Hide resolved
packages/compiler/src/render3/view/util.ts Outdated Show resolved Hide resolved
packages/compiler/src/render3/view/util.ts Outdated Show resolved Hide resolved
packages/compiler/src/render3/view/util.ts Outdated Show resolved Hide resolved
packages/core/test/authoring/type_tester.ts Outdated Show resolved Hide resolved
packages/core/test/authoring/type_tester.ts Show resolved Hide resolved
packages/compiler-cli/test/ngtsc/authoring_spec.ts Outdated Show resolved Hide resolved
alxhub
alxhub previously requested changes Dec 15, 2023
packages/compiler/src/render3/view/util.ts Outdated Show resolved Hide resolved
packages/core/src/render3/definition.ts Outdated Show resolved Hide resolved
packages/core/src/render3/definition.ts Outdated Show resolved Hide resolved
packages/core/src/render3/instructions/shared.ts Outdated Show resolved Hide resolved
packages/core/src/authoring/input_signal_node.ts Outdated Show resolved Hide resolved
@devversion
Copy link
Member Author

@alxhub @JoostK Pushed all addressed feedback as fixup commits (in between commits needed for order of changes). All individual commits auto-squashed should pass in order.

@@ -14,7 +14,7 @@ import {initNgDevMode} from './ng_dev_mode';
* code.
*/

export const EMPTY_OBJ: {} = {};
export const EMPTY_OBJ: never = {} as never;

Choose a reason for hiding this comment

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

👍

// import preserved to simplify `.d.ts` emit and simplify the `type_tester` logic.
import {InputSignal} from '../../src/authoring/input_signal';

export class InputSignatureTest {

Choose a reason for hiding this comment

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

Those tests are super-useful! 👍

import {applyValueToInputField} from '../apply_value_input_field';
import {DirectiveDef, InputFlags} from '../interfaces/definition';

export function writeToDirectiveInput<T>(

Choose a reason for hiding this comment

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

super-nit: this is not enforced / followed today but the initial idea was that the instructions folder would be just the entry point to the instructions called from the generated code with the actual logic living in the main renderer3 folder (in the, say inputs.ts).

Curious about your take on it.

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes sense. Yeah, it's doesn't seem fully compliant right now either, but I agree. Will follow-up on that in separate PR.

Copy link
Member

@pkozlowski-opensource pkozlowski-opensource left a comment

Choose a reason for hiding this comment

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

LGTM

Reviewed-for: public-api
Reviewed-for: size-tracking
Reviewed-for: fw-core

@devversion devversion force-pushed the signal-pr2 branch 4 times, most recently from 8d48415 to 925d652 Compare December 20, 2023 13:13
rlmestre pushed a commit to rlmestre/angular that referenced this pull request Jan 26, 2024
… compiler output (angular#53571)

Instead of computing the bit input flags at compile-time and inling
the final bit flag number, we will use the `InputFlags` enum directly.
This is a little more code in the compiler side, but will allow us to
have better debuggable development code, and also prevents problems
where runtime flag bitmasks differ from the compiler flag bitmasks.

This is in practice a noop for optimized applications as the enum values
would be inlined anyway. This matches existing compiler emit for e.g.
change detection strategy, or view encapsulation enums.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…inputs (angular#53571)

This commit adds additional type-check transform tests for signal
inputs. These tests verify some of the problems with covariance,
contravariance and bivariance that we were suspecting to be problematic
if we would assign `InputSignal`'s directly to the type constructors.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
angular#53571)

The `SignalNode` interface, describing the reactive node for a `Signal`,
seemingly exposes the `SIGNAL` symbol as a class member. This is not
true as the `SIGNAL` reactive node only exists on the getter function,
as a way to retrieve the signal underlying reactive node.

This commit fixes this, enabling improved type-safety later, in a
follow-up commit where `SIGNAL_NODE` can now be typed to match the
`SignalNode` interface (unlike now where it's typed as just `object`).

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
… of signals (angular#53571)

For the implementation of input signals, we want to extend the signal
primitive. The basic methods exposed here are not suitable as we'd like
to store additional metadata on the reactive node, and also have a
custom getter (for required inputs and throwing).

To enable this, one small piece was missing. This commit exposes it and
also improves type safety, now that `SignalNode` is typed properly after
the previous commit.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…ar#53571)

This commit introduces the runtime `InputSignal` implementation.
Input initializers using `input` or `input.required` will result in
an instance of `InputSignal` to be created.

An input signal extends the signal primtive, with a couple of small
differences:

 - it's a readonly signal. There is no public `set` or `update`.
 - equality is non-configurable. As per CD semantics, the value is
   guaranteed to be different when the `property` instruction attempts
   to update an input signal.
 - we support a `transform` function, that allows transforming input
   values. The transform is called whenever the input is set. An
   alternative could have been to follow computed-semantics and call the
   transform upon accessing, if dirty.

In the future, we might change this to extend the computed reactive
node, so that we can support computed inputs that do not rely on
continious bound value assignments. See signal based components RFC.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…ignals (angular#53571)

At this point, we have the following pieces in place:

* the input signature is implemented
* the compiler properly parses and recognizes signal inputs
* the compiler supports type-checking of signal inputs
* input signal metadata is passed to partial output

This commit adds a naive runtime solution to distinguishing between
signal inputs and decorator inputs when the `property` instruction
invokes. This is not ideal and non-performant as we introduce additional
megamorphic reads for every property instruction invocation, or if we'd
use `instanceof`, introducing a hard dependency on `InputSignal` and
risking potentially slower detection.

This code exists purely for testing, to enable playing with input
signals in the playground. In a future commit, we will pass around the
input signal metadata at runtime and can perform highly optimized checks
to distinguish between signal or non-signal inputs- when assigning
values.

More information: https://docs.google.com/document/d/1FpnFruviKb6BFTQfMAP2AMEqEB0FI7z-3mT_qm7lzX8/edit#heading=h.oloxympe902x

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…en (angular#53571)

Currently when a base class defines an input with a transform, derived
classes re-defining the input via `@Input`, or `inputs: [<..>]`, end up
inherting the transform due to a bug in the inherit definitions feature.

This commit fixes this. We verified in the Google codebase that this is
an unlikely occurrence and it's trivial to fix on user side by removing
the re-declaration/override, or explictly adding the necessary
transform.

Conceptually, the behavior was quite inconsistent as everything else of
inputs was overridden as expected. i.e. alias, required state etc. The
exception were input transforms. This commit fixes this.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…gular#53571)

This commit introduces a new enum for capturing additional metadata
about inputs. Called `InputFlags`. These will be built up at compile
time and then propagated into the runtime logic, in a way that does
not require additional lookup dictionaries data structures, or
additional memory allocations for "common inputs" that do not have any flags.

The flags will incorporate information on whether an input is signal
based. This can then be used to avoid megamorphic accesses when such
input is set- as we'd not need to check the input field value. This also
avoids cases where an input signal may be used as initial value for an
input (as we'd not incorrectly detect the input as a signal input then).

The new metadata emit will be useful for incorporating additional
metadata for inputs, such as whether they are required etc (although
required inputs are a build-time only construct right now- but this is a
good illustration of why input flags can be useful). An alternative
could have been to have an additional boolean entry for signal inputs,
but allocating a number with more flexible input flags seems more future
proof and more reasonable andreadable.

More information on the megamorphic access when updating an input
signal
https://docs.google.com/document/d/1FpnFruviKb6BFTQfMAP2AMEqEB0FI7z-3mT_qm7lzX8/edit.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…ngular#53571)

Consider a snippet like:

```
const x = directiveDef.inputs || EMPTY_OBJ
```

this currently results in `x` being inferred as just `{}`- ending up
turning of potential future assignment checks. This surfaced in the
`DirectiveDefinition` -> `DirectiveDef` conversion.

Note: This has the effect that assigning `EMPTY_OBJ` to a field of
anything would _always_ pass. It's questionable if this rather impacts
type-safety in a more negative way. There seem to be trade-offs in both
ways... Maybe worth considering just using `{}` directly as fallbacks in
some places, and treating this as an unique symbol?!

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAHgLhmApgNxQJxgXhgbwF8YBDCZdLAbgCgbRJYAHJfGmDmAGxC6SkwBXFABoahAD6CwAExQAzAJaoZuZIK5dS5EmACeteuGgxBapjAkT4VIA

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
Adds tests that allow us to ensure that the `input` API works as
expected and that resulting return types match our expectations- without
silently regressing in the future, or missing potential edge-cases.

Testing signatures is hard because of covariance and contravariance,
especially when it comes to the different semantics of `ReadT` and
`WriteT` of input signals. We enable reliable testing by validating the
`d.ts` of the "fake directive class". This ensures clear results,
compared to relying on e.g. type assertions that might
accidentally/silently pass due to covariance/contravariance or
biavariance in the type system.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…53571)

This commit adds a final test for input signals, integrating all major
parts:

* type-checking
* compiler detection
* compiler emit
* API signature tests

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
…lar#53571)

The linker compliance tests did not run for a while. There were a couple
of new tests that were not passing as this wasn't flagged on CI. This commit fixes this.
Fortunately there was no problematic code that did indicate issues with linking.

In the follow-up commit, we fix the compliance test infrastructure to
re-enable linker testing..

One clear issue is still that the defer blocks are not handled properly
in linked output- hence making defer not actually "lazy" for compiled
libraries. This needs to be handled separately by the framework team.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
The linker compliance tests were disabled with a Babel update and
nobody realized for quite a while, via
angular#49914.

As we've came across this lost coverage, which also is quite
impactful as all libraries depend on linked output- I've took initiative
to debug the root cause as there was no follow-up.
angular#51647.

It turned out to be a highly complex issue that is non-trivial to fix,
but at least we should try to resurrect the significant portion of test
coverage by still running the linker tests- avoiding regressions, or
unexpected issues (like with defer being developed). We can work on
re-enabling and fixing source-maps separately.

Tracked via angular#51647.

PR Close angular#53571
danieljancar pushed a commit to danieljancar/angular that referenced this pull request Jan 26, 2024
… compiler output (angular#53571)

Instead of computing the bit input flags at compile-time and inling
the final bit flag number, we will use the `InputFlags` enum directly.
This is a little more code in the compiler side, but will allow us to
have better debuggable development code, and also prevents problems
where runtime flag bitmasks differ from the compiler flag bitmasks.

This is in practice a noop for optimized applications as the enum values
would be inlined anyway. This matches existing compiler emit for e.g.
change detection strategy, or view encapsulation enums.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…inputs (angular#53571)

This commit adds additional type-check transform tests for signal
inputs. These tests verify some of the problems with covariance,
contravariance and bivariance that we were suspecting to be problematic
if we would assign `InputSignal`'s directly to the type constructors.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
angular#53571)

The `SignalNode` interface, describing the reactive node for a `Signal`,
seemingly exposes the `SIGNAL` symbol as a class member. This is not
true as the `SIGNAL` reactive node only exists on the getter function,
as a way to retrieve the signal underlying reactive node.

This commit fixes this, enabling improved type-safety later, in a
follow-up commit where `SIGNAL_NODE` can now be typed to match the
`SignalNode` interface (unlike now where it's typed as just `object`).

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
… of signals (angular#53571)

For the implementation of input signals, we want to extend the signal
primitive. The basic methods exposed here are not suitable as we'd like
to store additional metadata on the reactive node, and also have a
custom getter (for required inputs and throwing).

To enable this, one small piece was missing. This commit exposes it and
also improves type safety, now that `SignalNode` is typed properly after
the previous commit.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…ar#53571)

This commit introduces the runtime `InputSignal` implementation.
Input initializers using `input` or `input.required` will result in
an instance of `InputSignal` to be created.

An input signal extends the signal primtive, with a couple of small
differences:

 - it's a readonly signal. There is no public `set` or `update`.
 - equality is non-configurable. As per CD semantics, the value is
   guaranteed to be different when the `property` instruction attempts
   to update an input signal.
 - we support a `transform` function, that allows transforming input
   values. The transform is called whenever the input is set. An
   alternative could have been to follow computed-semantics and call the
   transform upon accessing, if dirty.

In the future, we might change this to extend the computed reactive
node, so that we can support computed inputs that do not rely on
continious bound value assignments. See signal based components RFC.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…ignals (angular#53571)

At this point, we have the following pieces in place:

* the input signature is implemented
* the compiler properly parses and recognizes signal inputs
* the compiler supports type-checking of signal inputs
* input signal metadata is passed to partial output

This commit adds a naive runtime solution to distinguishing between
signal inputs and decorator inputs when the `property` instruction
invokes. This is not ideal and non-performant as we introduce additional
megamorphic reads for every property instruction invocation, or if we'd
use `instanceof`, introducing a hard dependency on `InputSignal` and
risking potentially slower detection.

This code exists purely for testing, to enable playing with input
signals in the playground. In a future commit, we will pass around the
input signal metadata at runtime and can perform highly optimized checks
to distinguish between signal or non-signal inputs- when assigning
values.

More information: https://docs.google.com/document/d/1FpnFruviKb6BFTQfMAP2AMEqEB0FI7z-3mT_qm7lzX8/edit#heading=h.oloxympe902x

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…en (angular#53571)

Currently when a base class defines an input with a transform, derived
classes re-defining the input via `@Input`, or `inputs: [<..>]`, end up
inherting the transform due to a bug in the inherit definitions feature.

This commit fixes this. We verified in the Google codebase that this is
an unlikely occurrence and it's trivial to fix on user side by removing
the re-declaration/override, or explictly adding the necessary
transform.

Conceptually, the behavior was quite inconsistent as everything else of
inputs was overridden as expected. i.e. alias, required state etc. The
exception were input transforms. This commit fixes this.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…gular#53571)

This commit introduces a new enum for capturing additional metadata
about inputs. Called `InputFlags`. These will be built up at compile
time and then propagated into the runtime logic, in a way that does
not require additional lookup dictionaries data structures, or
additional memory allocations for "common inputs" that do not have any flags.

The flags will incorporate information on whether an input is signal
based. This can then be used to avoid megamorphic accesses when such
input is set- as we'd not need to check the input field value. This also
avoids cases where an input signal may be used as initial value for an
input (as we'd not incorrectly detect the input as a signal input then).

The new metadata emit will be useful for incorporating additional
metadata for inputs, such as whether they are required etc (although
required inputs are a build-time only construct right now- but this is a
good illustration of why input flags can be useful). An alternative
could have been to have an additional boolean entry for signal inputs,
but allocating a number with more flexible input flags seems more future
proof and more reasonable andreadable.

More information on the megamorphic access when updating an input
signal
https://docs.google.com/document/d/1FpnFruviKb6BFTQfMAP2AMEqEB0FI7z-3mT_qm7lzX8/edit.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…ngular#53571)

Consider a snippet like:

```
const x = directiveDef.inputs || EMPTY_OBJ
```

this currently results in `x` being inferred as just `{}`- ending up
turning of potential future assignment checks. This surfaced in the
`DirectiveDefinition` -> `DirectiveDef` conversion.

Note: This has the effect that assigning `EMPTY_OBJ` to a field of
anything would _always_ pass. It's questionable if this rather impacts
type-safety in a more negative way. There seem to be trade-offs in both
ways... Maybe worth considering just using `{}` directly as fallbacks in
some places, and treating this as an unique symbol?!

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAHgLhmApgNxQJxgXhgbwF8YBDCZdLAbgCgbRJYAHJfGmDmAGxC6SkwBXFABoahAD6CwAExQAzAJaoZuZIK5dS5EmACeteuGgxBapjAkT4VIA

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
Adds tests that allow us to ensure that the `input` API works as
expected and that resulting return types match our expectations- without
silently regressing in the future, or missing potential edge-cases.

Testing signatures is hard because of covariance and contravariance,
especially when it comes to the different semantics of `ReadT` and
`WriteT` of input signals. We enable reliable testing by validating the
`d.ts` of the "fake directive class". This ensures clear results,
compared to relying on e.g. type assertions that might
accidentally/silently pass due to covariance/contravariance or
biavariance in the type system.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…53571)

This commit adds a final test for input signals, integrating all major
parts:

* type-checking
* compiler detection
* compiler emit
* API signature tests

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
…lar#53571)

The linker compliance tests did not run for a while. There were a couple
of new tests that were not passing as this wasn't flagged on CI. This commit fixes this.
Fortunately there was no problematic code that did indicate issues with linking.

In the follow-up commit, we fix the compliance test infrastructure to
re-enable linker testing..

One clear issue is still that the defer blocks are not handled properly
in linked output- hence making defer not actually "lazy" for compiled
libraries. This needs to be handled separately by the framework team.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
The linker compliance tests were disabled with a Babel update and
nobody realized for quite a while, via
angular#49914.

As we've came across this lost coverage, which also is quite
impactful as all libraries depend on linked output- I've took initiative
to debug the root cause as there was no follow-up.
angular#51647.

It turned out to be a highly complex issue that is non-trivial to fix,
but at least we should try to resurrect the significant portion of test
coverage by still running the linker tests- avoiding regressions, or
unexpected issues (like with defer being developed). We can work on
re-enabling and fixing source-maps separately.

Tracked via angular#51647.

PR Close angular#53571
amilamen pushed a commit to amilamen/angular that referenced this pull request Jan 26, 2024
… compiler output (angular#53571)

Instead of computing the bit input flags at compile-time and inling
the final bit flag number, we will use the `InputFlags` enum directly.
This is a little more code in the compiler side, but will allow us to
have better debuggable development code, and also prevents problems
where runtime flag bitmasks differ from the compiler flag bitmasks.

This is in practice a noop for optimized applications as the enum values
would be inlined anyway. This matches existing compiler emit for e.g.
change detection strategy, or view encapsulation enums.

PR Close angular#53571
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: build & ci Related the build and CI infrastructure of the project area: compiler Issues related to `ngc`, Angular's template compiler area: core Issues related to the framework runtime cross-cutting: signals PullApprove: disable target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants