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

ES2022 Class fields and static blocks #1546

Merged
merged 24 commits into from
May 9, 2023
11 changes: 3 additions & 8 deletions Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@
"Array.fromAsync",
"async-iteration",
"Atomics",
"class-fields-private",
"class-fields-public",
"class-methods-private",
"class-static-block",
"class-static-fields-private",
"class-static-fields-public",
"class-static-methods-private",
"decorators",
"generators",
"import-assertions",
Expand Down Expand Up @@ -76,9 +69,9 @@
"built-ins/RegExp/unicode_restricted_identity_escape.js",
"built-ins/RegExp/unicode_restricted_quantifiable_assertion.js",


// requires investigation how to process complex function name evaluation for property
"built-ins/Function/prototype/toString/method-computed-property-name.js",
"language/expressions/class/elements/class-name-static-initializer-anonymous.js",

// http://www.ecma-international.org/ecma-262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics not implemented (block level functions)
"language/statements/let/block-local-closure-set-before-initialization.js",
Expand Down Expand Up @@ -118,6 +111,8 @@
"language/expressions/object/accessor-name-computed.js",
"built-ins/TypedArrayConstructors/ctors/object-arg/as-generator-iterable-returns.js",
"language/expressions/object/method-definition/name-prop-name-yield-id.js",
"language/statements/class/elements/*-generator-method-*.js",
"language/expressions/class/elements/*-generator-method-*.js",

// generators not implemented
"built-ins/Object/prototype/toString/proxy-function.js",
Expand Down
4 changes: 2 additions & 2 deletions Jint.Tests/Runtime/NullPropagation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class NullPropagationReferenceResolver : IReferenceResolver
{
public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value)
{
value = reference.GetBase();
value = reference.Base;
return true;
}

Expand All @@ -24,7 +24,7 @@ public bool TryGetCallable(Engine engine, object callee, out JsValue value)
{
if (callee is Reference reference)
{
var name = reference.GetReferencedName().AsString();
var name = reference.ReferencedName.AsString();
if (name == "filter")
{
value = new ClrFunctionInstance(engine, "map", (thisObj, values) => engine.Realm.Intrinsics.Array.ArrayCreate(0));
Expand Down
80 changes: 57 additions & 23 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ namespace Jint
/// </summary>
public sealed partial class Engine : IDisposable
{
private readonly JavaScriptParser _defaultParser = new(ParserOptions.Default);
private static readonly ParserOptions _defaultParserOptions = ParserOptions.Default with
{
AllowReturnOutsideFunction = true
};
private readonly JavaScriptParser _defaultParser = new(_defaultParserOptions);

internal readonly ExecutionContextStack _executionContexts;
private JsValue _completionValue = JsValue.Undefined;
Expand Down Expand Up @@ -286,13 +290,13 @@ public void ResetCallStack()
/// Evaluates code and returns last return value.
/// </summary>
public JsValue Evaluate(string code)
=> Evaluate(code, "<anonymous>", ParserOptions.Default);
=> Evaluate(code, "<anonymous>", _defaultParserOptions);

/// <summary>
/// Evaluates code and returns last return value.
/// </summary>
public JsValue Evaluate(string code, string source)
=> Evaluate(code, source, ParserOptions.Default);
=> Evaluate(code, source, _defaultParserOptions);

/// <summary>
/// Evaluates code and returns last return value.
Expand All @@ -305,7 +309,7 @@ public JsValue Evaluate(string code, ParserOptions parserOptions)
/// </summary>
public JsValue Evaluate(string code, string source, ParserOptions parserOptions)
{
var parser = ReferenceEquals(ParserOptions.Default, parserOptions)
var parser = ReferenceEquals(_defaultParserOptions, parserOptions)
? _defaultParser
: new JavaScriptParser(parserOptions);

Expand All @@ -324,7 +328,7 @@ public JsValue Evaluate(Script script)
/// Executes code into engine and returns the engine instance (useful for chaining).
/// </summary>
public Engine Execute(string code, string? source = null)
=> Execute(code, source ?? "<anonymous>", ParserOptions.Default);
=> Execute(code, source ?? "<anonymous>", _defaultParserOptions);

/// <summary>
/// Executes code into engine and returns the engine instance (useful for chaining).
Expand All @@ -337,7 +341,7 @@ public Engine Execute(string code, ParserOptions parserOptions)
/// </summary>
public Engine Execute(string code, string source, ParserOptions parserOptions)
{
var parser = ReferenceEquals(ParserOptions.Default, parserOptions)
var parser = ReferenceEquals(_defaultParserOptions, parserOptions)
? _defaultParser
: new JavaScriptParser(parserOptions);

Expand Down Expand Up @@ -506,7 +510,7 @@ internal JsValue GetValue(object value, bool returnReferenceToPool)

internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
{
var baseValue = reference.GetBase();
var baseValue = reference.Base;

if (baseValue.IsUndefined())
{
Expand All @@ -524,18 +528,24 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
return baseValue;
}

if (reference.IsPropertyReference())
if (reference.IsPropertyReference)
{
var property = reference.GetReferencedName();
var property = reference.ReferencedName;
if (returnReferenceToPool)
{
_referencePool.Return(reference);
}

if (baseValue.IsObject())
{
var o = TypeConverter.ToObject(Realm, baseValue);
var v = o.Get(property, reference.GetThisValue());
var baseObj = TypeConverter.ToObject(Realm, baseValue);

if (reference.IsPrivateReference)
{
return baseObj.PrivateGet((PrivateName) reference.ReferencedName);
}

var v = baseObj.Get(property, reference.ThisValue);
return v;
}
else
Expand All @@ -555,6 +565,11 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
o = TypeConverter.ToObject(Realm, baseValue);
}

if (reference.IsPrivateReference)
{
return o.PrivateGet((PrivateName) reference.ReferencedName);
}

var desc = o.GetProperty(property);
if (desc == PropertyDescriptor.Undefined)
{
Expand Down Expand Up @@ -583,7 +598,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
ExceptionHelper.ThrowArgumentException();
}

var bindingValue = record.GetBindingValue(reference.GetReferencedName().ToString(), reference.IsStrictReference());
var bindingValue = record.GetBindingValue(reference.ReferencedName.ToString(), reference.Strict);

if (returnReferenceToPool)
{
Expand Down Expand Up @@ -632,32 +647,33 @@ private bool TryHandleStringValue(JsValue property, JsString s, ref ObjectInstan
/// </summary>
internal void PutValue(Reference reference, JsValue value)
{
var baseValue = reference.GetBase();
if (reference.IsUnresolvableReference())
if (reference.IsUnresolvableReference)
{
if (reference.IsStrictReference() && reference.GetReferencedName() != CommonProperties.Arguments)
if (reference.Strict && reference.ReferencedName != CommonProperties.Arguments)
{
ExceptionHelper.ThrowReferenceError(Realm, reference);
}

Realm.GlobalObject.Set(reference.GetReferencedName(), value, throwOnError: false);
Realm.GlobalObject.Set(reference.ReferencedName, value, throwOnError: false);
}
else if (reference.IsPropertyReference())
else if (reference.IsPropertyReference)
{
if (reference.HasPrimitiveBase())
var baseObject = TypeConverter.ToObject(Realm, reference.Base);
if (reference.IsPrivateReference)
{
baseValue = TypeConverter.ToObject(Realm, baseValue);
baseObject.PrivateSet((PrivateName) reference.ReferencedName, value);
return;
}

var succeeded = baseValue.Set(reference.GetReferencedName(), value, reference.GetThisValue());
if (!succeeded && reference.IsStrictReference())
var succeeded = baseObject.Set(reference.ReferencedName, value, reference.ThisValue);
if (!succeeded && reference.Strict)
{
ExceptionHelper.ThrowTypeError(Realm, "Cannot assign to read only property '" + reference.GetReferencedName() + "' of " + baseValue);
ExceptionHelper.ThrowTypeError(Realm, "Cannot assign to read only property '" + reference.ReferencedName + "' of " + baseObject);
}
}
else
{
((EnvironmentRecord) baseValue).SetMutableBinding(TypeConverter.ToString(reference.GetReferencedName()), value, reference.IsStrictReference());
((EnvironmentRecord) reference.Base).SetMutableBinding(TypeConverter.ToString(reference.ReferencedName), value, reference.Strict);
}
}

Expand Down Expand Up @@ -1197,6 +1213,21 @@ private ArgumentsInstance CreateUnmappedArgumentsObject(JsValue[] argumentsList)
}
}

HashSet<PrivateIdentifier>? privateIdentifiers = null;
var pointer = privateEnv;
while (pointer is not null)
{
foreach (var name in pointer.Names)
{
privateIdentifiers??= new HashSet<PrivateIdentifier>(PrivateIdentifierNameComparer._instance);
privateIdentifiers.Add(name.Key);
}

pointer = pointer.OuterPrivateEnvironment;
}

script.AllPrivateIdentifiersValid(realm, privateIdentifiers);

var functionDeclarations = hoistingScope._functionDeclarations;
var functionsToInitialize = new LinkedList<JintFunctionDefinition>();
var declaredFunctionNames = new HashSet<string>();
Expand Down Expand Up @@ -1433,6 +1464,9 @@ ObjectInstance Callback()
return ((IConstructor) constructor).Construct(arguments, newTarget);
}

internal JsValue Call(FunctionInstance functionInstance, JsValue thisObject)
=> Call(functionInstance, thisObject, Arguments.Empty, null);

internal JsValue Call(
FunctionInstance functionInstance,
JsValue thisObject,
Expand Down
95 changes: 86 additions & 9 deletions Jint/EsprimaExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
using Esprima;
using Esprima.Ast;
using Esprima.Utils;
using Jint.Native;
using Jint.Native.Function;
using Jint.Native.Object;
Expand Down Expand Up @@ -44,6 +45,10 @@ public static JsValue GetKey(this Expression expression, Engine engine, bool res
{
key = identifier.Name;
}
else if (expression is PrivateIdentifier privateIdentifier)
{
key = engine.ExecutionContext.PrivateEnvironment!.Names[privateIdentifier];
}
else if (resolveComputed)
{
return TryGetComputedPropertyKey(expression, engine);
Expand Down Expand Up @@ -243,11 +248,37 @@ internal static void GetBoundNames(this Node? parameter, List<string> target)
target.Add(name);
}
}

break;
}
}

/// <summary>
/// https://tc39.es/ecma262/#sec-static-semantics-privateboundidentifiers
/// </summary>
internal static void PrivateBoundIdentifiers(this Node parameter, HashSet<PrivateIdentifier> target)
{
if (parameter.Type == Nodes.PrivateIdentifier)
{
target.Add((PrivateIdentifier) parameter);
}
else if (parameter.Type is Nodes.AccessorProperty or Nodes.MethodDefinition or Nodes.PropertyDefinition)
{
if (((ClassProperty) parameter).Key is PrivateIdentifier privateKeyIdentifier)
{
target.Add(privateKeyIdentifier);
}
}
else if (parameter.Type == Nodes.ClassBody)
{
ref readonly var elements = ref ((ClassBody) parameter).Body;
for (var i = 0; i < elements.Count; i++)
{
var element = elements[i];
PrivateBoundIdentifiers(element, target);
}
}
}

internal static void BindingInitialization(
this Node? expression,
EvaluationContext context,
Expand Down Expand Up @@ -279,8 +310,8 @@ internal static void GetBoundNames(this Node? parameter, List<string> target)
var intrinsics = engine.Realm.Intrinsics;

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

var prototype = functionPrototype ?? intrinsics.Function.PrototypeObject;
var function = m.Value as IFunction;
Expand All @@ -290,7 +321,7 @@ internal static void GetBoundNames(this Node? parameter, List<string> target)
}

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

return new Record(propKey, closure);
Expand Down Expand Up @@ -438,13 +469,59 @@ internal static SyntaxElement CreateLocationNode(in Location location)
{
return new MinimalSyntaxElement(location);
}
}

internal sealed class MinimalSyntaxElement : SyntaxElement
{
public MinimalSyntaxElement(in Location location)
/// <summary>
/// https://tc39.es/ecma262/#sec-static-semantics-allprivateidentifiersvalid
/// </summary>
internal static void AllPrivateIdentifiersValid(this Script script, Realm realm, HashSet<PrivateIdentifier>? privateIdentifiers)
{
Location = location;
var validator = new PrivateIdentifierValidator(realm, privateIdentifiers);
validator.Visit(script);
}

private sealed class MinimalSyntaxElement : SyntaxElement
{
public MinimalSyntaxElement(in Location location)
{
Location = location;
}
}

private sealed class PrivateIdentifierValidator : AstVisitor
{
private readonly Realm _realm;
private HashSet<PrivateIdentifier>? _privateNames;

public PrivateIdentifierValidator(Realm realm, HashSet<PrivateIdentifier>? privateNames)
{
_realm = realm;
_privateNames = privateNames;
}

protected override object VisitPrivateIdentifier(PrivateIdentifier privateIdentifier)
{
if (_privateNames is null || !_privateNames.Contains(privateIdentifier))
{
Throw(_realm, privateIdentifier);
}
return privateIdentifier;
}

protected override object VisitClassBody(ClassBody classBody)
{
var oldList = _privateNames;
_privateNames = new HashSet<PrivateIdentifier>(PrivateIdentifierNameComparer._instance);
classBody.PrivateBoundIdentifiers(_privateNames);
base.VisitClassBody(classBody);
_privateNames = oldList;
return classBody;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void Throw(Realm r, PrivateIdentifier id)
{
ExceptionHelper.ThrowSyntaxError(r, $"Private field '#{id.Name}' must be declared in an enclosing class");
}
}
}
}