diff --git a/docs/unified-bytecode-expansion-contract.md b/docs/unified-bytecode-expansion-contract.md index 9256ff9a9..cd82c3fdf 100644 --- a/docs/unified-bytecode-expansion-contract.md +++ b/docs/unified-bytecode-expansion-contract.md @@ -329,7 +329,7 @@ must still obey the no-mixed-execution rule. | `ArrowLexicalThisDependency` | Activation descriptor gate for arrow lexical `this` / `new.target` ownership before ordinary sync routing | Existing arrow invocation route | Arrow route lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_OrdinarySyncActivationDescriptorBlockers_DeclineBeforeCompile"` | | `ClassConstructorActivation` | Activation descriptor gate for class constructor activation outside the admitted simple base constructor route and explicit derived-constructor `super(...)` route with post-super `this` body reads/writes | Constructor route | Constructor boundary lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionConstructCallTests&FullyQualifiedName~Constructor"` | | `CallDependency` | Direct eval outside the one-argument non-spread eval-identifier boundary, out-of-boundary call-target preparation, complex call arguments excluding admitted simple/binary template-literal substitutions, simple/binary computed object keys, and zero-argument activation-resolved identifier-call computed object keys, and descriptor-level non-parameter callee calls | Existing sync IR call route | Wider call invocation lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ActivationDependencies_DeclineBeforeCompile"` | -| `DynamicLookupDependency` | Unresolved identifier loads/stores/typeof/update outside the with-backed dynamic-name path | Existing sync IR / environment lookup route | Dynamic-name lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_DynamicIdentifierLookup_DeclinesWithDynamicLookupDependency"` | +| `DynamicLookupDependency` | Unresolved identifier loads/stores/update outside the admitted ordinary and with-backed dynamic-name paths | Existing sync IR / environment lookup route | Dynamic-name lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_DynamicIdentifierLookup_DeclinesWithDynamicLookupDependency"` | | `PropertyReadBoundaryOutOfScope` | Named/computed property reads outside the admitted activation-resolved boundaries | Existing sync IR property route | Property read widening lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_ComputedPropertyReadOutsideFirstBoundary_DeclinesWithBoundaryCode"` | | `PropertyWriteDependency` | Property writes and compound/logical property writes outside the admitted direct property-write shapes, supported computed expression-key mutation shapes, simple nested named receiver assignment shape, nested named compound-write shape, and nested named logical-write shape | Existing sync IR property-write route | Property write widening lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_LogicalAndAssignment_UnsupportedShapes_DeclineWithExplicitCodes"` | | `PropertyUpdateDependency` | Property and identifier update expressions outside the admitted direct update, computed expression-key update, and simple nested named receiver update boundary | Existing sync IR update route | Property update lane | `rtk dotnet test tests/Asynkron.JsEngine.Tests --filter "FullyQualifiedName~UnifiedBytecodeProductionEligibilityTests&FullyQualifiedName~Evaluate_NestedNamedPropertyUpdate_AcceptsOwnedPropertyOpcodes"` | @@ -837,10 +837,11 @@ support today. or descriptor-backed assignment lanes remain outside the admitted boundary (`DestructuringDependency`). 6. Dynamic lookup families remain outside the admitted boundary - (`DynamicLookupDependency`) except for the explicit with-backed dynamic name - slice above. Direct eval outside the admitted one-argument non-spread - eval-identifier boundary, unresolved non-with lookup shapes, and captured - dynamic activation still decline before VM execution. + (`DynamicLookupDependency`) except for the ordinary dynamic-name environment + path and the explicit with-backed dynamic name slice above. Direct eval + outside the admitted one-argument non-spread eval-identifier boundary, + unresolved dynamic activation, and unsupported unresolved lookup shapes still + decline before VM execution. 7. Label-dependent control flow is now admitted (ADR 0285): labeled statements, labeled loops, labeled block `break`, and labeled `break`/`continue` route through the compiler-owned resolved-target path. The remaining diff --git a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs index e987a124c..4861acbec 100644 --- a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs +++ b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionEligibilityTests.cs @@ -4338,7 +4338,7 @@ function kind(value) { } [Fact] - public void Evaluate_TypeOfUnresolvedIdentifier_DeclinesWithDynamicLookupDependency() + public void Evaluate_TypeOfUnresolvedIdentifier_AcceptsOrdinaryDynamicNameOpcode() { var plan = GetFunctionPlan(""" function kind() { @@ -4349,11 +4349,13 @@ function kind() { var result = UnifiedBytecodeProductionEligibility.Evaluate( plan, - new UnifiedBytecodeProductionActivationDescriptor()); + new UnifiedBytecodeProductionActivationDescriptor( + AllowsOrdinaryDynamicIdentifierEnvironmentOperations: true)); - Assert.False(result.IsEligible); - Assert.Equal(UnifiedBytecodeProductionDeclineCode.DynamicLookupDependency, result.Code); - Assert.Contains("typeof identifier 'missing'", result.Reason, StringComparison.Ordinal); + Assert.True(result.IsEligible, result.Reason); + Assert.Equal(UnifiedBytecodeProductionDeclineCode.None, result.Code); + Assert.Contains(result.Program.Instructions, instruction => + instruction.OpCode == UnifiedBytecodeOpCode.TypeOfDynamicIdentifier); } [Fact] diff --git a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs index 2007805fa..ad8f47939 100644 --- a/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs +++ b/tests/Asynkron.JsEngine.Tests/UnifiedBytecodeProductionInvocationTests.cs @@ -3144,6 +3144,25 @@ static record => record.Message.Contains( StringComparison.Ordinal)); } + [Fact(Timeout = 5000)] + public async Task TypeOfUnresolvedIdentifier_UsesUnifiedBytecodeProductionFastPath() + { + await using var engine = CreateEngine(); + var result = await engine.Evaluate(""" + function kind() { + return typeof missing; + } + + kind(); + """); + + Assert.Equal("undefined", result?.ToString()); + Assert.Contains(CurrentLogger!.Collector.Snapshot(), + static record => record.Message.Contains( + "unified-bytecode-production-fast-path func=kind argc=0", + StringComparison.Ordinal)); + } + [Fact(Timeout = 5000)] public async Task TypeOfIdentifierForLexicalTdz_PropagatesReferenceErrorThroughUnifiedBytecodeProductionFastPath() {