Skip to content

Commit

Permalink
Bring function construction closer to the spec (#1170)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored May 20, 2022
1 parent d4621d6 commit 3bc93cf
Show file tree
Hide file tree
Showing 32 changed files with 622 additions and 255 deletions.
2 changes: 1 addition & 1 deletion Jint.Tests.Test262/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"test262harness.console": {
"version": "0.0.11",
"version": "0.0.13",
"commands": [
"test262"
]
Expand Down
2 changes: 1 addition & 1 deletion Jint.Tests.Test262/Jint.Tests.Test262.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="Test262Harness" Version="0.0.11" />
<PackageReference Include="Test262Harness" Version="0.0.13" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
Expand Down
5 changes: 5 additions & 0 deletions Jint.Tests.Test262/Test262Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,9 @@ private static void ExecuteTest(Engine engine, Test262File file)
engine.Execute(new JavaScriptParser(file.Program, new ParserOptions(file.FileName)).ParseScript());
}
}

private partial bool ShouldThrow(Test262File testCase, bool strict)
{
return testCase.Negative;
}
}
18 changes: 10 additions & 8 deletions Jint/Collections/RefStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ public RefStack(int capacity = DefaultCapacity)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref readonly T Peek()
{
if (_size == 0)
{
ExceptionHelper.ThrowInvalidOperationException("stack is empty");
}

return ref _array[_size - 1];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref readonly T Peek(int fromTop)
{
var index = _size - 1 - fromTop;
return ref _array[index];
}

public T this[int index] => _array[index];

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -155,7 +157,7 @@ public bool MoveNext()
{
bool returnValue;
if (_index == -2)
{
{
// First call to enumerator.
_index = _stack._size - 1;
returnValue = (_index >= 0);
Expand All @@ -167,7 +169,7 @@ public bool MoveNext()
}

if (_index == -1)
{
{
// End of enumeration.
return false;
}
Expand All @@ -192,7 +194,7 @@ void IEnumerator.Reset()
{
_index = -2;
_currentElement = default;
}
}
}
}
}
13 changes: 10 additions & 3 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,7 @@ private void GlobalDeclarationInstantiation(
}
}

PrivateEnvironmentRecord privateEnv = null;
if (lexDeclarations != null)
{
for (var i = 0; i < lexDeclarations.Count; i++)
Expand Down Expand Up @@ -879,7 +880,7 @@ private void GlobalDeclarationInstantiation(
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{fn}' has already been declared");
}

var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, env);
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, env, privateEnv);
env.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false);
}

Expand Down Expand Up @@ -1023,7 +1024,7 @@ internal ArgumentsInstance FunctionDeclarationInstantiation(
foreach (var f in configuration.FunctionsToInitialize)
{
var fn = f.Name;
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, lexEnv);
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, lexEnv, privateEnv);
varEnv.SetMutableBinding(fn, fo, strict: false);
}
}
Expand Down Expand Up @@ -1180,7 +1181,7 @@ internal void EvalDeclarationInstantiation(
foreach (var f in functionsToInitialize)
{
var fn = f.Name;
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, lexEnv);
var fo = realm.Intrinsics.Function.InstantiateFunctionObject(f, lexEnv, privateEnv);
if (varEnvRec is GlobalEnvironmentRecord ger)
{
ger.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: true);
Expand Down Expand Up @@ -1230,6 +1231,12 @@ internal void UpdateVariableEnvironment(EnvironmentRecord newEnv)
_executionContexts.ReplaceTopVariableEnvironment(newEnv);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdatePrivateEnvironment(PrivateEnvironmentRecord newEnv)
{
_executionContexts.ReplaceTopPrivateEnvironment(newEnv);
}

/// <summary>
/// Invokes the named callable and returns the resulting object.
/// </summary>
Expand Down
79 changes: 49 additions & 30 deletions Jint/EsprimaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,51 @@ public static class EsprimaExtensions

public static JsValue GetKey(this Expression expression, Engine engine, bool resolveComputed = false)
{
var completion = TryGetKey(expression, engine, resolveComputed);
if (completion.Value is not null)
{
return TypeConverter.ToPropertyKey(completion.Value);
}

ExceptionHelper.ThrowArgumentException("Unable to extract correct key, node type: " + expression.Type);
return JsValue.Undefined;
}

internal static Completion TryGetKey(this ClassProperty property, Engine engine)
{
return TryGetKey(property.Key, engine, property.Computed);
}

internal static Completion TryGetKey(this Expression expression, Engine engine, bool resolveComputed)
{
JsValue key;
if (expression is Literal literal)
{
if (literal.TokenType == TokenType.NullLiteral)
{
return JsValue.Null;
key = JsValue.Null;
}
else
{
key = LiteralKeyToString(literal);
}

return LiteralKeyToString(literal);
}

if (!resolveComputed && expression is Identifier identifier)
else if (!resolveComputed && expression is Identifier identifier)
{
return identifier.Name;
key = identifier.Name;
}

if (!resolveComputed || !TryGetComputedPropertyKey(expression, engine, out var propertyKey))
else if (resolveComputed)
{
ExceptionHelper.ThrowArgumentException("Unable to extract correct key, node type: " + expression.Type);
return null;
return TryGetComputedPropertyKey(expression, engine);
}

return propertyKey;
else
{
key = JsValue.Undefined;
}
return new Completion(CompletionType.Normal, key, expression.Location);
}

private static bool TryGetComputedPropertyKey<T>(T expression, Engine engine, out JsValue propertyKey)
private static Completion TryGetComputedPropertyKey<T>(T expression, Engine engine)
where T : Expression
{
if (expression.Type is Nodes.Identifier
Expand All @@ -59,15 +79,14 @@ or Nodes.MemberExpression
or Nodes.LogicalExpression
or Nodes.ConditionalExpression
or Nodes.ArrowFunctionExpression
or Nodes.FunctionExpression)
or Nodes.FunctionExpression
or Nodes.YieldExpression)
{
var context = engine._activeEvaluationContext;
propertyKey = TypeConverter.ToPropertyKey(JintExpression.Build(engine, expression).GetValue(context).Value);
return true;
return JintExpression.Build(engine, expression).GetValue(context);
}

propertyKey = string.Empty;
return false;
return new Completion(CompletionType.Normal, JsValue.Undefined, expression.Location);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -266,25 +285,25 @@ internal static void BindingInitialization(
internal static Record DefineMethod(this ClassProperty m, ObjectInstance obj, ObjectInstance? functionPrototype = null)
{
var engine = obj.Engine;
var property = TypeConverter.ToPropertyKey(m.GetKey(engine));
var prototype = functionPrototype ?? engine.Realm.Intrinsics.Function.PrototypeObject;
var propKey = TypeConverter.ToPropertyKey(m.GetKey(engine));
var intrinsics = engine.Realm.Intrinsics;

var runningExecutionContext = engine.ExecutionContext;
var scope = runningExecutionContext.LexicalEnvironment;
var privateScope= runningExecutionContext.PrivateEnvironment;

var prototype = functionPrototype ?? intrinsics.Function.PrototypeObject;
var function = m.Value as IFunction;
if (function is null)
{
ExceptionHelper.ThrowSyntaxError(engine.Realm);
}

var functionDefinition = new JintFunctionDefinition(engine, function);
var closure = new ScriptFunctionInstance(
engine,
functionDefinition,
engine.ExecutionContext.LexicalEnvironment,
functionDefinition.ThisMode,
prototype);

var definition = new JintFunctionDefinition(engine, function);
var closure = intrinsics.Function.OrdinaryFunctionCreate(prototype, definition, definition.ThisMode, scope, privateScope);
closure.MakeMethod(obj);

return new Record(property, closure);
return new Record(propKey, closure);
}

internal static void GetImportEntries(this ImportDeclaration import, List<ImportEntry> importEntries, HashSet<string> requestedModules)
Expand Down Expand Up @@ -422,4 +441,4 @@ private static List<string> GetExportNames(StatementListItem declaration)

internal readonly record struct Record(JsValue Key, ScriptFunctionInstance Closure);
}
}
}
59 changes: 51 additions & 8 deletions Jint/Native/Function/ClassDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,42 @@ public ClassDefinition(
_body = body;
}

public void Initialize()
{

}

/// <summary>
/// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
/// </summary>
public ScriptFunctionInstance BuildConstructor(
public Completion BuildConstructor(
EvaluationContext context,
EnvironmentRecord env)
{
// A class definition is always strict mode code.
using var _ = new StrictModeScope(true, true);

var engine = context.Engine;

var classScope = JintEnvironment.NewDeclarativeEnvironment(engine, env);

if (_className is not null)
{
classScope.CreateImmutableBinding(_className, true);
}

var outerPrivateEnvironment = engine.ExecutionContext.PrivateEnvironment;
var classPrivateEnvironment = JintEnvironment.NewPrivateEnvironment(engine, outerPrivateEnvironment);

/*
6. If ClassBodyopt is present, then
a. For each String dn of the PrivateBoundIdentifiers of ClassBodyopt, do
i. If classPrivateEnvironment.[[Names]] contains a Private Name whose [[Description]] is dn, then
1. Assert: This is only possible for getter/setter pairs.
ii. Else,
1. Let name be a new Private Name whose [[Description]] value is dn.
2. Append name to classPrivateEnvironment.[[Names]].
*/

ObjectInstance? protoParent = null;
ObjectInstance? constructorParent = null;
if (_superClass is null)
Expand Down Expand Up @@ -99,7 +116,7 @@ public ScriptFunctionInstance BuildConstructor(
else
{
ExceptionHelper.ThrowTypeError(engine.Realm, "cannot resolve super class prototype chain");
return null!;
return default;
}

constructorParent = (ObjectInstance) superclass;
Expand Down Expand Up @@ -151,26 +168,45 @@ public ScriptFunctionInstance BuildConstructor(
}

var target = !m.Static ? proto : F;
PropertyDefinitionEvaluation(engine, target, m);
var completion = MethodDefinitionEvaluation(engine, target, m);
if (completion.IsAbrupt())
{
return completion;
}
}
}
finally
{
engine.UpdateLexicalEnvironment(env);
engine.UpdatePrivateEnvironment(outerPrivateEnvironment);
}

if (_className is not null)
{
classScope.InitializeBinding(_className, F);
}

return F;
/*
28. Set F.[[PrivateMethods]] to instancePrivateMethods.
29. Set F.[[Fields]] to instanceFields.
30. For each PrivateElement method of staticPrivateMethods, do
a. Perform ! PrivateMethodOrAccessorAdd(method, F).
31. For each element fieldRecord of staticFields, do
a. Let result be DefineField(F, fieldRecord).
b. If result is an abrupt completion, then
i. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
ii. Return result.
*/

engine.UpdatePrivateEnvironment(outerPrivateEnvironment);

return new Completion(CompletionType.Normal, F, _body.Location);
}

/// <summary>
/// https://tc39.es/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
/// https://tc39.es/ecma262/#sec-runtime-semantics-methoddefinitionevaluation
/// </summary>
private static void PropertyDefinitionEvaluation(
private static Completion MethodDefinitionEvaluation(
Engine engine,
ObjectInstance obj,
MethodDefinition method)
Expand All @@ -184,7 +220,12 @@ private static void PropertyDefinitionEvaluation(
}
else
{
var propKey = TypeConverter.ToPropertyKey(method.GetKey(engine));
var completion = method.TryGetKey(engine);
if (completion.IsAbrupt())
{
return completion;
}
var propKey = TypeConverter.ToPropertyKey(completion.Value);
var function = method.Value as IFunction;
if (function is null)
{
Expand All @@ -207,6 +248,8 @@ private static void PropertyDefinitionEvaluation(

obj.DefinePropertyOrThrow(propKey, propDesc);
}

return new Completion(CompletionType.Normal, obj, method.Location);
}
}
}
Loading

0 comments on commit 3bc93cf

Please sign in to comment.