diff --git a/source/units/Goccia.AST.Statements.pas b/source/units/Goccia.AST.Statements.pas index a8764369..231e7253 100644 --- a/source/units/Goccia.AST.Statements.pas +++ b/source/units/Goccia.AST.Statements.pas @@ -296,6 +296,10 @@ TGocciaClassElement = record TGocciaFieldOrderEntry = record Name: string; IsPrivate: Boolean; + IsComputed: Boolean; + ElementIndex: Integer; + ComputedKeyExpression: TGocciaExpression; + FieldInitializer: TGocciaExpression; end; // Shared class definition structure diff --git a/source/units/Goccia.Bytecode.OpCodeNames.pas b/source/units/Goccia.Bytecode.OpCodeNames.pas index f0587279..6a1196dc 100644 --- a/source/units/Goccia.Bytecode.OpCodeNames.pas +++ b/source/units/Goccia.Bytecode.OpCodeNames.pas @@ -143,12 +143,14 @@ function OpCodeName(const AOp: UInt8): string; OP_DEFINE_STATIC_METHOD_CONST: Result := 'OP_DEFINE_STATIC_METHOD_CONST'; OP_DEFINE_DATA_PROP: Result := 'OP_DEFINE_DATA_PROP'; OP_DEFINE_METHOD_PROP: Result := 'OP_DEFINE_METHOD_PROP'; + OP_DEFINE_PROP_DYNAMIC: Result := 'OP_DEFINE_PROP_DYNAMIC'; OP_ADD: Result := 'OP_ADD'; OP_SUB: Result := 'OP_SUB'; OP_MUL: Result := 'OP_MUL'; OP_DIV: Result := 'OP_DIV'; OP_MOD: Result := 'OP_MOD'; OP_POW: Result := 'OP_POW'; + OP_SETUP_AUTO_ACCESSOR_DYNAMIC: Result := 'OP_SETUP_AUTO_ACCESSOR_DYNAMIC'; OP_BAND: Result := 'OP_BAND'; OP_BOR: Result := 'OP_BOR'; OP_BXOR: Result := 'OP_BXOR'; @@ -170,6 +172,7 @@ function OpCodeName(const AOp: UInt8): string; OP_CREATE_ARGUMENTS: Result := 'OP_CREATE_ARGUMENTS'; OP_TO_OBJECT: Result := 'OP_TO_OBJECT'; OP_HAS_WITH_BINDING: Result := 'OP_HAS_WITH_BINDING'; + OP_TO_PROPERTY_KEY: Result := 'OP_TO_PROPERTY_KEY'; OP_INC: Result := 'OP_INC'; OP_DEC: Result := 'OP_DEC'; OP_TO_NUMERIC: Result := 'OP_TO_NUMERIC'; diff --git a/source/units/Goccia.Bytecode.pas b/source/units/Goccia.Bytecode.pas index e2f3d690..4cd2173e 100644 --- a/source/units/Goccia.Bytecode.pas +++ b/source/units/Goccia.Bytecode.pas @@ -57,7 +57,13 @@ interface // own data properties instead of assigning through prototypes. // v33 -> v34: added OP_DEFINE_METHOD_PROP so concise object methods get // [[HomeObject]] without affecting plain data properties. - GOCCIA_FORMAT_VERSION = 34; + // v34 -> v35: added OP_DEFINE_PROP_DYNAMIC for computed public + // class fields. + // v35 -> v36: added OP_TO_PROPERTY_KEY so delayed computed class field + // definitions can reuse source-order property keys. + // v36 -> v37: added OP_SETUP_AUTO_ACCESSOR_DYNAMIC for computed + // auto-accessor keys. + GOCCIA_FORMAT_VERSION = 37; GOCCIA_BINARY_MAGIC: array[0..3] of Byte = (Ord('G'), Ord('B'), Ord('C'), 0); GOCCIA_NULLISH_MATCH_UNDEFINED = 0; GOCCIA_NULLISH_MATCH_NULL = 1; @@ -222,12 +228,14 @@ interface OP_DEFINE_STATIC_METHOD_CONST = 124, OP_DEFINE_DATA_PROP = 125, OP_DEFINE_METHOD_PROP = 126, + OP_DEFINE_PROP_DYNAMIC = 127, OP_ADD = 128, OP_SUB = 129, OP_MUL = 130, OP_DIV = 131, OP_MOD = 132, OP_POW = 133, + OP_SETUP_AUTO_ACCESSOR_DYNAMIC = 134, OP_BAND = 135, OP_BOR = 136, OP_BXOR = 137, @@ -251,7 +259,8 @@ interface OP_NEW_TARGET = 181, OP_CREATE_ARGUMENTS = 182, OP_TO_OBJECT = 183, - OP_HAS_WITH_BINDING = 184 + OP_HAS_WITH_BINDING = 184, + OP_TO_PROPERTY_KEY = 185 ); function EncodeABC(const AOp: TGocciaOpCode; const A, B, C: UInt8): UInt32; inline; diff --git a/source/units/Goccia.Compiler.Statements.pas b/source/units/Goccia.Compiler.Statements.pas index 2f205924..244b86ac 100644 --- a/source/units/Goccia.Compiler.Statements.pas +++ b/source/units/Goccia.Compiler.Statements.pas @@ -129,6 +129,13 @@ TPreallocatedUsingDisposeSlot = record ResourceRegistered: Boolean; end; + TComputedFieldKeyLocal = record + ElementIndex: Integer; + Name: string; + end; + + TComputedFieldKeyLocals = array of TComputedFieldKeyLocal; + TPendingFinallyEntry = record FinallyBlock: TGocciaBlockStatement; // Non-nil when this entry represents a using block's disposal. @@ -3680,23 +3687,66 @@ procedure CompileComputedMethodBody(const ACtx: TGocciaCompilationContext; ACtx.Scope.FreeRegister; end; +function FindComputedFieldKeyLocalName( + const ALocals: TComputedFieldKeyLocals; + const AElementIndex: Integer): string; +var + LocalIndex: Integer; +begin + for LocalIndex := 0 to High(ALocals) do + if ALocals[LocalIndex].ElementIndex = AElementIndex then + Exit(ALocals[LocalIndex].Name); + Result := ''; +end; + procedure CompileComputedElements(const ACtx: TGocciaCompilationContext; - const ATargetReg: UInt8; const AClassDef: TGocciaClassDefinition); + const ATargetReg: UInt8; const AClassDef: TGocciaClassDefinition; + var AComputedFieldKeyLocals: TComputedFieldKeyLocals); var I: Integer; Elem: TGocciaClassElement; KeyReg: UInt8; + ComputedKeyName: string; + ClassKeyPrefix: string; + NeedsKeyLocal: Boolean; + KeyIsLocal: Boolean; begin + SetLength(AComputedFieldKeyLocals, 0); + ClassKeyPrefix := IntToHex(PtrUInt(AClassDef), SizeOf(PtrUInt) * 2); for I := 0 to High(AClassDef.FElements) do begin Elem := AClassDef.FElements[I]; if not Elem.IsComputed then Continue; + if not (Elem.Kind in [cekGetter, cekSetter, cekMethod, cekField, cekAccessor]) then + Continue; - KeyReg := ACtx.Scope.AllocateRegister; + NeedsKeyLocal := (Elem.Kind in [cekField, cekAccessor]) or + (Length(Elem.Decorators) > 0); + KeyIsLocal := False; + if NeedsKeyLocal then + begin + SetLength(AComputedFieldKeyLocals, Length(AComputedFieldKeyLocals) + 1); + ComputedKeyName := Format('#computed-element-key:%s:%d', + [ClassKeyPrefix, I]); + AComputedFieldKeyLocals[High(AComputedFieldKeyLocals)].ElementIndex := I; + AComputedFieldKeyLocals[High(AComputedFieldKeyLocals)].Name := + ComputedKeyName; + KeyReg := ACtx.Scope.DeclareLocal(ComputedKeyName, False); + KeyIsLocal := True; + end + else + KeyReg := ACtx.Scope.AllocateRegister; ACtx.CompileExpression(Elem.ComputedKeyExpression, KeyReg); + if (Elem.Kind in [cekField, cekAccessor]) or + (Length(Elem.Decorators) > 0) then + EmitInstruction(ACtx, EncodeABC(OP_TO_PROPERTY_KEY, KeyReg, KeyReg, 0)); case Elem.Kind of + cekField: + begin + Continue; + end; cekGetter: if Elem.IsStatic then CompileComputedGetterBody(ACtx, ATargetReg, KeyReg, @@ -3715,9 +3765,12 @@ procedure CompileComputedElements(const ACtx: TGocciaCompilationContext; cekMethod: CompileComputedMethodBody(ACtx, ATargetReg, KeyReg, Elem.MethodNode, Elem.IsStatic); + cekAccessor: + ; // Auto-accessor installation consumes the captured property key later. end; - ACtx.Scope.FreeRegister; + if not KeyIsLocal then + ACtx.Scope.FreeRegister; end; end; @@ -3733,8 +3786,20 @@ function HasAccessorInitializers( Result := False; end; +function HasComputedInstanceFields( + const AClassDef: TGocciaClassDefinition): Boolean; +var + I: Integer; +begin + for I := 0 to High(AClassDef.FFieldOrder) do + if AClassDef.FFieldOrder[I].IsComputed then + Exit(True); + Result := False; +end; + procedure CompileFieldInitializer(const ACtx: TGocciaCompilationContext; - const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition); + const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition; + const AComputedFieldKeyLocals: TComputedFieldKeyLocals); var OldTemplate: TGocciaFunctionTemplate; OldScope: TGocciaCompilerScope; @@ -3743,12 +3808,14 @@ procedure CompileFieldInitializer(const ACtx: TGocciaCompilationContext; ChildCtx: TGocciaCompilationContext; FuncIdx: UInt16; FnReg: UInt8; - ValReg, ThisReg: UInt8; + ValReg, ThisReg, KeyReg: UInt8; KeyIdx: UInt16; - I: Integer; + I, UpvalueIdx: Integer; Entry: TGocciaExpressionMap.TKeyValuePair; Elem: TGocciaClassElement; FieldExpr: TGocciaExpression; + ComputedKeyName: string; + AccessorBackingName: string; begin OldTemplate := ACtx.Template; OldScope := ACtx.Scope; @@ -3773,7 +3840,27 @@ procedure CompileFieldInitializer(const ACtx: TGocciaCompilationContext; for I := 0 to High(AClassDef.FFieldOrder) do begin ValReg := ChildScope.AllocateRegister; - if AClassDef.FFieldOrder[I].IsPrivate then + if AClassDef.FFieldOrder[I].IsComputed then + begin + if Assigned(AClassDef.FFieldOrder[I].FieldInitializer) then + ACtx.CompileExpression(AClassDef.FFieldOrder[I].FieldInitializer, ValReg) + else + EmitInstruction(ChildCtx, EncodeABx(OP_LOAD_UNDEFINED, ValReg, 0)); + KeyReg := ChildScope.AllocateRegister; + ComputedKeyName := FindComputedFieldKeyLocalName( + AComputedFieldKeyLocals, AClassDef.FFieldOrder[I].ElementIndex); + UpvalueIdx := ChildScope.ResolveUpvalue(ComputedKeyName); + if UpvalueIdx < 0 then + raise Exception.Create('Compiler error: computed class field key was not captured'); + EmitInstruction(ChildCtx, EncodeABx(OP_GET_UPVALUE, KeyReg, + UInt16(UpvalueIdx))); + EmitInstruction(ChildCtx, EncodeABC(OP_DEFINE_PROP_DYNAMIC, ThisReg, + KeyReg, ValReg)); + ChildScope.FreeRegister; + ChildScope.FreeRegister; + Continue; + end + else if AClassDef.FFieldOrder[I].IsPrivate then begin if AClassDef.PrivateInstanceProperties.TryGetValue( AClassDef.FFieldOrder[I].Name, FieldExpr) then @@ -3831,7 +3918,11 @@ procedure CompileFieldInitializer(const ACtx: TGocciaCompilationContext; Continue; ValReg := ChildScope.AllocateRegister; ACtx.CompileExpression(Elem.FieldInitializer, ValReg); - KeyIdx := ChildTemplate.AddConstantString('__accessor_' + Elem.Name); + if Elem.IsComputed then + AccessorBackingName := '__accessor_computed_' + IntToStr(I) + else + AccessorBackingName := '__accessor_' + Elem.Name; + KeyIdx := ChildTemplate.AddConstantString(AccessorBackingName); if KeyIdx > High(UInt8) then raise Exception.Create('Constant pool overflow: accessor backing field name index exceeds 255'); EmitInstruction(ChildCtx, EncodeABC(OP_SET_PROP_CONST, ThisReg, @@ -3973,12 +4064,16 @@ function HasDecoratorsOrAccessors( end; procedure CompileAutoAccessors(const ACtx: TGocciaCompilationContext; - const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition); + const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition; + const AComputedFieldKeyLocals: TComputedFieldKeyLocals); var I: Integer; Elem: TGocciaClassElement; NameIdx: UInt16; - PairReg, InitReg: UInt8; + KeyReg: UInt8; + LocalIdx: Integer; + ComputedKeyName: string; + BackingName: string; begin for I := 0 to High(AClassDef.FElements) do begin @@ -3986,23 +4081,36 @@ procedure CompileAutoAccessors(const ACtx: TGocciaCompilationContext; if Elem.Kind <> cekAccessor then Continue; - NameIdx := ACtx.Template.AddConstantString(Elem.Name); + if Elem.IsComputed then + BackingName := '__accessor_computed_' + IntToStr(I) + else + BackingName := Elem.Name; + + NameIdx := ACtx.Template.AddConstantString(BackingName); if NameIdx > High(UInt8) then raise Exception.Create('Constant pool overflow: accessor name index exceeds 255'); - PairReg := ACtx.Scope.AllocateRegister; - InitReg := ACtx.Scope.AllocateRegister; - EmitInstruction(ACtx, EncodeABC(OP_LOAD_UNDEFINED, InitReg, 0, 0)); - EmitInstruction(ACtx, EncodeABC(OP_SETUP_AUTO_ACCESSOR_CONST, PairReg, 0, - UInt8(NameIdx))); - ACtx.Scope.FreeRegister; - ACtx.Scope.FreeRegister; + if Elem.IsComputed then + begin + ComputedKeyName := FindComputedFieldKeyLocalName( + AComputedFieldKeyLocals, I); + LocalIdx := ACtx.Scope.ResolveLocal(ComputedKeyName); + if LocalIdx < 0 then + raise Exception.Create('Compiler error: computed auto-accessor key was not captured'); + KeyReg := ACtx.Scope.GetLocal(LocalIdx).Slot; + EmitInstruction(ACtx, EncodeABC(OP_SETUP_AUTO_ACCESSOR_DYNAMIC, + KeyReg, Ord(Elem.IsStatic), UInt8(NameIdx))); + end + else + EmitInstruction(ACtx, EncodeABC(OP_SETUP_AUTO_ACCESSOR_CONST, + 0, Ord(Elem.IsStatic), UInt8(NameIdx))); end; end; procedure CompileDecoratorOrchestration( const ACtx: TGocciaCompilationContext; - const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition); + const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition; + const AComputedFieldKeyLocals: TComputedFieldKeyLocals); var I, J: Integer; Elem: TGocciaClassElement; @@ -4012,6 +4120,8 @@ procedure CompileDecoratorOrchestration( Desc: string; PairReg, ExtraReg: UInt8; HasElementDecorators: Boolean; + ComputedKeyName: string; + LocalIdx: Integer; begin HasElementDecorators := False; for I := 0 to High(AClassDef.FElements) do @@ -4060,9 +4170,24 @@ procedure CompileDecoratorOrchestration( PairReg := ACtx.Scope.AllocateRegister; ExtraReg := ACtx.Scope.AllocateRegister; EmitInstruction(ACtx, EncodeABC(OP_MOVE, PairReg, DecoRegs[I][J], 0)); - EmitInstruction(ACtx, EncodeABC(OP_LOAD_UNDEFINED, ExtraReg, 0, 0)); - EmitInstruction(ACtx, EncodeABC(OP_APPLY_ELEMENT_DECORATOR_CONST, PairReg, - 0, UInt8(DescIdx))); + if Elem.IsComputed then + begin + ComputedKeyName := FindComputedFieldKeyLocalName( + AComputedFieldKeyLocals, I); + LocalIdx := ACtx.Scope.ResolveLocal(ComputedKeyName); + if LocalIdx < 0 then + raise Exception.Create('Compiler error: computed decorator element key was not captured'); + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ExtraReg, + ACtx.Scope.GetLocal(LocalIdx).Slot, 0)); + EmitInstruction(ACtx, EncodeABC(OP_APPLY_ELEMENT_DECORATOR_CONST, + PairReg, ExtraReg, UInt8(DescIdx))); + end + else + begin + EmitInstruction(ACtx, EncodeABC(OP_LOAD_UNDEFINED, ExtraReg, 0, 0)); + EmitInstruction(ACtx, EncodeABC(OP_APPLY_ELEMENT_DECORATOR_CONST, + PairReg, 0, UInt8(DescIdx))); + end; ACtx.Scope.FreeRegister; ACtx.Scope.FreeRegister; end; @@ -4089,7 +4214,8 @@ procedure CompileDecoratorOrchestration( procedure CompileDecoratorAndAccessorPass( const ACtx: TGocciaCompilationContext; const AClassReg: UInt8; const AClassDef: TGocciaClassDefinition; - const ASuperReg: Integer); + const ASuperReg: Integer; + const AComputedFieldKeyLocals: TComputedFieldKeyLocals); var PairReg, ExtraReg: UInt8; begin @@ -4107,8 +4233,10 @@ procedure CompileDecoratorAndAccessorPass( ACtx.Scope.FreeRegister; ACtx.Scope.FreeRegister; - CompileAutoAccessors(ACtx, AClassReg, AClassDef); - CompileDecoratorOrchestration(ACtx, AClassReg, AClassDef); + CompileAutoAccessors(ACtx, AClassReg, AClassDef, + AComputedFieldKeyLocals); + CompileDecoratorOrchestration(ACtx, AClassReg, AClassDef, + AComputedFieldKeyLocals); PairReg := ACtx.Scope.AllocateRegister; ExtraReg := ACtx.Scope.AllocateRegister; @@ -4124,7 +4252,7 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; const AStmt: TGocciaClassDeclaration); var ClassDef: TGocciaClassDefinition; - ClassReg, SuperReg, ValReg: UInt8; + ClassReg, SuperReg, ValReg, KeyReg: UInt8; NameIdx, KeyIdx: UInt16; MethodPair: TGocciaClassMethodMap.TKeyValuePair; GetterPair: TGocciaGetterExpressionMap.TKeyValuePair; @@ -4133,6 +4261,8 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; I, LocalIdx, UpvalIdx: Integer; HasSuper: Boolean; PrivPrefix: string; + ComputedFieldKeyLocals: TComputedFieldKeyLocals; + ComputedKeyName: string; begin ClassDef := AStmt.ClassDefinition; HasSuper := ClassDef.SuperClass <> ''; @@ -4237,10 +4367,13 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; SetterPair.Value, OP_DEFINE_ACCESSOR_CONST, ACCESSOR_FLAG_STATIC or ACCESSOR_FLAG_SETTER); end; + CompileComputedElements(ACtx, ClassReg, ClassDef, ComputedFieldKeyLocals); + if (ClassDef.InstanceProperties.Count > 0) or (ClassDef.PrivateInstanceProperties.Count > 0) or + HasComputedInstanceFields(ClassDef) or HasAccessorInitializers(ClassDef) then - CompileFieldInitializer(ACtx, ClassReg, ClassDef); + CompileFieldInitializer(ACtx, ClassReg, ClassDef, ComputedFieldKeyLocals); // Static fields without FElements entries (legacy / no static blocks) if Length(ClassDef.FElements) = 0 then @@ -4272,8 +4405,6 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; end; end; - CompileComputedElements(ACtx, ClassReg, ClassDef); - // ES2022 §15.7.14: compile static fields and static blocks in source order for I := 0 to High(ClassDef.FElements) do begin @@ -4282,6 +4413,17 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; else if (ClassDef.FElements[I].Kind = cekField) and ClassDef.FElements[I].IsStatic then begin ValReg := ACtx.Scope.AllocateRegister; + if ClassDef.FElements[I].IsComputed then + begin + ComputedKeyName := FindComputedFieldKeyLocalName( + ComputedFieldKeyLocals, I); + LocalIdx := ACtx.Scope.ResolveLocal(ComputedKeyName); + if LocalIdx < 0 then + raise Exception.Create('Compiler error: computed static field key was not captured'); + KeyReg := ACtx.Scope.GetLocal(LocalIdx).Slot; + end + else + KeyReg := 0; if Assigned(ClassDef.FElements[I].FieldInitializer) then ACtx.CompileExpression(ClassDef.FElements[I].FieldInitializer, ValReg) else @@ -4297,6 +4439,9 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; EmitInstruction(ACtx, EncodeABC(OP_SET_PROP_CONST, ClassReg, UInt8(KeyIdx), ValReg)); end + else if ClassDef.FElements[I].IsComputed then + EmitInstruction(ACtx, EncodeABC(OP_DEFINE_PROP_DYNAMIC, + ClassReg, KeyReg, ValReg)) else begin KeyIdx := ACtx.Template.AddConstantString(ClassDef.FElements[I].Name); @@ -4310,9 +4455,11 @@ procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; end; if HasSuper then - CompileDecoratorAndAccessorPass(ACtx, ClassReg, ClassDef, SuperReg) + CompileDecoratorAndAccessorPass(ACtx, ClassReg, ClassDef, SuperReg, + ComputedFieldKeyLocals) else - CompileDecoratorAndAccessorPass(ACtx, ClassReg, ClassDef, -1); + CompileDecoratorAndAccessorPass(ACtx, ClassReg, ClassDef, -1, + ComputedFieldKeyLocals); // Sync cell if the class local was pre-declared and captured by a hoisted // function (see CompileVariableDeclaration for the full explanation) @@ -4331,7 +4478,7 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; const AInferredName: string = ''); var ClassDef: TGocciaClassDefinition; - SuperReg, ValReg: UInt8; + SuperReg, ValReg, KeyReg: UInt8; NameIdx, KeyIdx: UInt16; MethodPair: TGocciaClassMethodMap.TKeyValuePair; GetterPair: TGocciaGetterExpressionMap.TKeyValuePair; @@ -4343,6 +4490,8 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; HasNameBinding: Boolean; ClosedLocals: array[0..0] of UInt8; ClosedCount, I: Integer; + ComputedFieldKeyLocals: TComputedFieldKeyLocals; + ComputedKeyName: string; begin ClassDef := AClassDef; HasSuper := ClassDef.SuperClass <> ''; @@ -4448,10 +4597,13 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; SetterPair.Value, OP_DEFINE_ACCESSOR_CONST, ACCESSOR_FLAG_STATIC or ACCESSOR_FLAG_SETTER); end; + CompileComputedElements(ACtx, ADest, ClassDef, ComputedFieldKeyLocals); + if (ClassDef.InstanceProperties.Count > 0) or (ClassDef.PrivateInstanceProperties.Count > 0) or + HasComputedInstanceFields(ClassDef) or HasAccessorInitializers(ClassDef) then - CompileFieldInitializer(ACtx, ADest, ClassDef); + CompileFieldInitializer(ACtx, ADest, ClassDef, ComputedFieldKeyLocals); // Static fields without FElements entries (legacy / no static blocks) if Length(ClassDef.FElements) = 0 then @@ -4483,8 +4635,6 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; end; end; - CompileComputedElements(ACtx, ADest, ClassDef); - // ES2022 §15.7.14: compile static fields and static blocks in source order for I := 0 to High(ClassDef.FElements) do begin @@ -4493,6 +4643,17 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; else if (ClassDef.FElements[I].Kind = cekField) and ClassDef.FElements[I].IsStatic then begin ValReg := ACtx.Scope.AllocateRegister; + if ClassDef.FElements[I].IsComputed then + begin + ComputedKeyName := FindComputedFieldKeyLocalName( + ComputedFieldKeyLocals, I); + LocalIdx := ACtx.Scope.ResolveLocal(ComputedKeyName); + if LocalIdx < 0 then + raise Exception.Create('Compiler error: computed static field key was not captured'); + KeyReg := ACtx.Scope.GetLocal(LocalIdx).Slot; + end + else + KeyReg := 0; if Assigned(ClassDef.FElements[I].FieldInitializer) then ACtx.CompileExpression(ClassDef.FElements[I].FieldInitializer, ValReg) else @@ -4508,6 +4669,9 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; EmitInstruction(ACtx, EncodeABC(OP_SET_PROP_CONST, ADest, UInt8(KeyIdx), ValReg)); end + else if ClassDef.FElements[I].IsComputed then + EmitInstruction(ACtx, EncodeABC(OP_DEFINE_PROP_DYNAMIC, + ADest, KeyReg, ValReg)) else begin KeyIdx := ACtx.Template.AddConstantString(ClassDef.FElements[I].Name); @@ -4522,7 +4686,8 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; if HasSuper then begin - CompileDecoratorAndAccessorPass(ACtx, ADest, ClassDef, SuperReg); + CompileDecoratorAndAccessorPass(ACtx, ADest, ClassDef, SuperReg, + ComputedFieldKeyLocals); // Only free __super__ manually when there is no name binding scope — // when HasNameBinding is true, __super__ lives inside the inner scope // and EndScope below will free it together with the name binding local. @@ -4530,7 +4695,8 @@ procedure CompileClassExpression(const ACtx: TGocciaCompilationContext; ACtx.Scope.FreeRegister; end else - CompileDecoratorAndAccessorPass(ACtx, ADest, ClassDef, -1); + CompileDecoratorAndAccessorPass(ACtx, ADest, ClassDef, -1, + ComputedFieldKeyLocals); if HasNameBinding then begin diff --git a/source/units/Goccia.Evaluator.Decorators.pas b/source/units/Goccia.Evaluator.Decorators.pas index 110cdd64..88161fed 100644 --- a/source/units/Goccia.Evaluator.Decorators.pas +++ b/source/units/Goccia.Evaluator.Decorators.pas @@ -22,23 +22,29 @@ TGocciaAccessGetter = class private FTarget: TGocciaValue; FPropertyName: string; + FPropertyKey: TGocciaValue; public constructor Create(const ATarget: TGocciaValue; const APropertyName: string); + constructor CreateWithKey(const ATarget: TGocciaValue; const APropertyKey: TGocciaValue); function Get(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; end; TGocciaAccessSetter = class private FPropertyName: string; + FPropertyKey: TGocciaValue; public constructor Create(const APropertyName: string); + constructor CreateWithKey(const APropertyKey: TGocciaValue); function SetValue(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; end; implementation uses - Goccia.Values.ObjectValue; + Goccia.Values.ClassValue, + Goccia.Values.ObjectValue, + Goccia.Values.SymbolValue; { TGocciaInitializerCollector } @@ -67,14 +73,43 @@ constructor TGocciaAccessGetter.Create(const ATarget: TGocciaValue; const APrope begin FTarget := ATarget; FPropertyName := APropertyName; + FPropertyKey := nil; +end; + +constructor TGocciaAccessGetter.CreateWithKey(const ATarget: TGocciaValue; const APropertyKey: TGocciaValue); +begin + FTarget := ATarget; + FPropertyKey := APropertyKey; + if Assigned(APropertyKey) and not (APropertyKey is TGocciaSymbolValue) then + FPropertyName := APropertyKey.ToStringLiteral.Value + else + FPropertyName := ''; end; function TGocciaAccessGetter.Get(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; +var + Target: TGocciaValue; begin - if Assigned(AThisValue) and (AThisValue is TGocciaObjectValue) then - Result := AThisValue.GetProperty(FPropertyName) - else if Assigned(FTarget) then - Result := FTarget.GetProperty(FPropertyName) + if AArgs.Length > 0 then + Target := AArgs.GetElement(0) + else if Assigned(AThisValue) and (AThisValue is TGocciaObjectValue) then + Target := AThisValue + else + Target := FTarget; + + if FPropertyKey is TGocciaSymbolValue then + begin + if Target is TGocciaClassValue then + Result := TGocciaClassValue(Target).GetSymbolProperty( + TGocciaSymbolValue(FPropertyKey)) + else if Target is TGocciaObjectValue then + Result := TGocciaObjectValue(Target).GetSymbolProperty( + TGocciaSymbolValue(FPropertyKey)) + else + Result := TGocciaUndefinedLiteralValue.UndefinedValue; + end + else if Assigned(Target) then + Result := Target.GetProperty(FPropertyName) else Result := TGocciaUndefinedLiteralValue.UndefinedValue; end; @@ -84,12 +119,53 @@ function TGocciaAccessGetter.Get(const AArgs: TGocciaArgumentsCollection; const constructor TGocciaAccessSetter.Create(const APropertyName: string); begin FPropertyName := APropertyName; + FPropertyKey := nil; +end; + +constructor TGocciaAccessSetter.CreateWithKey(const APropertyKey: TGocciaValue); +begin + FPropertyKey := APropertyKey; + if Assigned(APropertyKey) and not (APropertyKey is TGocciaSymbolValue) then + FPropertyName := APropertyKey.ToStringLiteral.Value + else + FPropertyName := ''; end; function TGocciaAccessSetter.SetValue(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; +var + Target: TGocciaValue; + NewValue: TGocciaValue; begin - if Assigned(AThisValue) and (AThisValue is TGocciaObjectValue) then - TGocciaObjectValue(AThisValue).AssignProperty(FPropertyName, AArgs.GetElement(0)); + if AArgs.Length >= 2 then + begin + Target := AArgs.GetElement(0); + NewValue := AArgs.GetElement(1); + end + else + begin + Target := AThisValue; + if AArgs.Length > 0 then + NewValue := AArgs.GetElement(0) + else + NewValue := TGocciaUndefinedLiteralValue.UndefinedValue; + end; + + if Target is TGocciaObjectValue then + begin + if FPropertyKey is TGocciaSymbolValue then + begin + if Target is TGocciaClassValue then + TGocciaClassValue(Target).AssignSymbolProperty( + TGocciaSymbolValue(FPropertyKey), NewValue) + else + TGocciaObjectValue(Target).AssignSymbolProperty( + TGocciaSymbolValue(FPropertyKey), NewValue); + end + else if Target is TGocciaClassValue then + TGocciaClassValue(Target).SetProperty(FPropertyName, NewValue) + else + TGocciaObjectValue(Target).AssignProperty(FPropertyName, NewValue); + end; Result := TGocciaUndefinedLiteralValue.UndefinedValue; end; diff --git a/source/units/Goccia.Evaluator.pas b/source/units/Goccia.Evaluator.pas index 857ad74b..37d70ac4 100644 --- a/source/units/Goccia.Evaluator.pas +++ b/source/units/Goccia.Evaluator.pas @@ -3526,7 +3526,23 @@ procedure InitializeInstanceProperties(const AInstance: TGocciaInstanceValue; co begin FOEntry := AClassValue.FieldOrderEntry(I); Expr := nil; - if FOEntry.IsPrivate then + if FOEntry.IsComputed then + begin + if Assigned(FOEntry.Initializer) then + PropertyValue := EvaluateExpression(FOEntry.Initializer, LocalContext) + else + PropertyValue := TGocciaUndefinedLiteralValue.UndefinedValue; + if FOEntry.ComputedKey is TGocciaSymbolValue then + AInstance.DefineSymbolProperty( + TGocciaSymbolValue(FOEntry.ComputedKey), + TGocciaPropertyDescriptorData.Create(PropertyValue, + [pfEnumerable, pfConfigurable, pfWritable])) + else if Assigned(FOEntry.ComputedKey) then + AInstance.DefineProperty(FOEntry.ComputedKey.ToStringLiteral.Value, + TGocciaPropertyDescriptorData.Create(PropertyValue, + [pfEnumerable, pfConfigurable, pfWritable])); + end + else if FOEntry.IsPrivate then begin if AClassValue.PrivateInstancePropertyDefs.TryGetValue(FOEntry.Name, Expr) and Assigned(Expr) then begin @@ -3590,7 +3606,23 @@ procedure InitializeObjectInstanceProperties(const AInstance: TGocciaObjectValue begin FOEntry := AClassValue.FieldOrderEntry(I); Expr := nil; - if FOEntry.IsPrivate then + if FOEntry.IsComputed then + begin + if Assigned(FOEntry.Initializer) then + PropertyValue := EvaluateExpression(FOEntry.Initializer, AContext) + else + PropertyValue := TGocciaUndefinedLiteralValue.UndefinedValue; + if FOEntry.ComputedKey is TGocciaSymbolValue then + AInstance.DefineSymbolProperty( + TGocciaSymbolValue(FOEntry.ComputedKey), + TGocciaPropertyDescriptorData.Create(PropertyValue, + [pfEnumerable, pfConfigurable, pfWritable])) + else if Assigned(FOEntry.ComputedKey) then + AInstance.DefineProperty(FOEntry.ComputedKey.ToStringLiteral.Value, + TGocciaPropertyDescriptorData.Create(PropertyValue, + [pfEnumerable, pfConfigurable, pfWritable])); + end + else if FOEntry.IsPrivate then begin if AClassValue.PrivateInstancePropertyDefs.TryGetValue(FOEntry.Name, Expr) and Assigned(Expr) then begin @@ -4152,7 +4184,10 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const ClassName: string; I, J: Integer; HasDecorators: Boolean; + HasReplayedComputedKey: Boolean; FieldOrderEntries: array of TGocciaClassFieldOrderEntry; + ResolvedComputedElementKeys: array of TGocciaValue; + Continuation: TGocciaGeneratorContinuation; MetadataObject: TGocciaObjectValue; SuperMetadata: TGocciaValue; EvaluatedElementDecorators: array of array of TGocciaValue; @@ -4162,9 +4197,12 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const ContextObject, AccessObject, AutoAccessorValue, DecResultObj: TGocciaObjectValue; Elem: TGocciaClassElement; ElementName: string; + ElementKey: TGocciaValue; CurrentMethod, GetterFnValue, SetterFnValue: TGocciaValue; NewGetter, NewSetter, NewInit: TGocciaValue; + ExistingGetterValue, ExistingSetterValue: TGocciaValue; MethodCollector, FieldCollector, StaticFieldCollector, ClassCollector: TGocciaInitializerCollector; + OriginalClassValue: TGocciaClassValue; AccessGetterHelper: TGocciaAccessGetter; AccessSetterHelper: TGocciaAccessSetter; InitializerResults: TArray; @@ -4184,7 +4222,93 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const ASetterExpression, AContext, MethodSuperClass, True)); TGocciaMethodValue(Result).OwningClass := ClassValue; end; + + function GetDecoratedDataProperty(const AIsStatic: Boolean; + const AName: string; const AKey: TGocciaValue): TGocciaValue; + begin + if AKey is TGocciaSymbolValue then + begin + if AIsStatic then + Result := ClassValue.GetSymbolProperty(TGocciaSymbolValue(AKey)) + else + Result := ClassValue.Prototype.GetSymbolProperty( + TGocciaSymbolValue(AKey)); + end + else if AIsStatic then + Result := ClassValue.GetProperty(AName) + else + Result := ClassValue.Prototype.GetProperty(AName); + end; + + procedure DefineDecoratedDataProperty(const AIsStatic: Boolean; + const AName: string; const AKey, AValue: TGocciaValue); + begin + if AKey is TGocciaSymbolValue then + begin + if AIsStatic then + ClassValue.DefineSymbolProperty( + TGocciaSymbolValue(AKey), + TGocciaPropertyDescriptorData.Create( + AValue, [pfConfigurable, pfWritable])) + else + ClassValue.Prototype.DefineSymbolProperty( + TGocciaSymbolValue(AKey), + TGocciaPropertyDescriptorData.Create( + AValue, [pfConfigurable, pfWritable])); + end + else if AIsStatic then + ClassValue.DefineProperty(AName, + TGocciaPropertyDescriptorData.Create( + AValue, [pfConfigurable, pfWritable])) + else + ClassValue.Prototype.AssignProperty(AName, AValue); + end; + + function GetDecoratedAccessorDescriptor(const AIsStatic: Boolean; + const AName: string; const AKey: TGocciaValue): TGocciaPropertyDescriptor; + begin + if AKey is TGocciaSymbolValue then + begin + if AIsStatic then + Result := ClassValue.GetOwnStaticSymbolDescriptor( + TGocciaSymbolValue(AKey)) + else + Result := ClassValue.Prototype.GetOwnSymbolPropertyDescriptor( + TGocciaSymbolValue(AKey)); + end + else if AIsStatic then + Result := ClassValue.GetOwnPropertyDescriptor(AName) + else + Result := ClassValue.Prototype.GetOwnPropertyDescriptor(AName); + end; + + procedure DefineDecoratedAccessorProperty(const AIsStatic: Boolean; + const AName: string; const AKey, AGetter, ASetter: TGocciaValue); + begin + if AKey is TGocciaSymbolValue then + begin + if AIsStatic then + ClassValue.DefineSymbolProperty( + TGocciaSymbolValue(AKey), + TGocciaPropertyDescriptorAccessor.Create( + AGetter, ASetter, [pfConfigurable])) + else + ClassValue.Prototype.DefineSymbolProperty( + TGocciaSymbolValue(AKey), + TGocciaPropertyDescriptorAccessor.Create( + AGetter, ASetter, [pfConfigurable])); + end + else if AIsStatic then + ClassValue.DefineProperty(AName, + TGocciaPropertyDescriptorAccessor.Create( + AGetter, ASetter, [pfConfigurable])) + else + ClassValue.Prototype.DefineProperty(AName, + TGocciaPropertyDescriptorAccessor.Create( + AGetter, ASetter, [pfConfigurable, pfWritable])); + end; begin + Continuation := CurrentGeneratorContinuation; SuperClass := nil; SuperClassValue := nil; MethodSuperClass := nil; @@ -4276,18 +4400,6 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const ClassValue.AddPrivateInstanceProperty(PropertyEntry.Key, PropertyEntry.Value); end; - // Copy field order for source-order initialization - if Length(AClassDef.FFieldOrder) > 0 then - begin - SetLength(FieldOrderEntries, Length(AClassDef.FFieldOrder)); - for I := 0 to High(AClassDef.FFieldOrder) do - begin - FieldOrderEntries[I].Name := AClassDef.FFieldOrder[I].Name; - FieldOrderEntries[I].IsPrivate := AClassDef.FFieldOrder[I].IsPrivate; - end; - ClassValue.SetFieldOrder(FieldOrderEntries); - end; - for MethodPair in AClassDef.PrivateMethods do begin Method := TGocciaMethodValue(EvaluateClassMethod(MethodPair.Value, AContext, MethodSuperClass)); @@ -4333,20 +4445,39 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const ClassValue.AddStaticSetter(SetterPair.Key, SetterFunction); end; - // Handle computed members (methods, getters, setters) in source order via FElements + SetLength(ResolvedComputedElementKeys, Length(AClassDef.FElements)); + + // Handle computed class element names in source order via FElements. for I := 0 to High(AClassDef.FElements) do begin Elem := AClassDef.FElements[I]; if not Elem.IsComputed then Continue; - if not (Elem.Kind in [cekMethod, cekGetter, cekSetter]) then + if not (Elem.Kind in [cekMethod, cekGetter, cekSetter, cekField, cekAccessor]) then Continue; // ES2026 §15.4 ClassDefinitionEvaluation step 6.b for computed names: // evaluate, then ToPropertyKey, dispatching string vs. symbol storage. - ComputedKey := ToPropertyKey(EvaluateExpression(Elem.ComputedKeyExpression, AContext)); + // Generator resumption restarts this class definition evaluation, so + // replay keys already resolved before a later computed name suspended. + HasReplayedComputedKey := False; + if Assigned(Continuation) then + HasReplayedComputedKey := Continuation.TakeCompletedExpressionValue( + Elem.ComputedKeyExpression, ComputedKey); + if not HasReplayedComputedKey then + ComputedKey := ToPropertyKey(EvaluateExpression( + Elem.ComputedKeyExpression, AContext)); + if Assigned(Continuation) then + Continuation.SaveCompletedExpressionValue( + Elem.ComputedKeyExpression, ComputedKey); + + ResolvedComputedElementKeys[I] := ComputedKey; case Elem.Kind of + cekField: + ; + cekAccessor: + ; cekMethod: begin Method := TGocciaMethodValue(EvaluateClassMethod(Elem.MethodNode, AContext, MethodSuperClass)); @@ -4442,18 +4573,52 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const end; end; + // Copy field order for source-order initialization after computed names are resolved. + if Length(AClassDef.FFieldOrder) > 0 then + begin + SetLength(FieldOrderEntries, Length(AClassDef.FFieldOrder)); + for I := 0 to High(AClassDef.FFieldOrder) do + begin + FieldOrderEntries[I].Name := AClassDef.FFieldOrder[I].Name; + FieldOrderEntries[I].IsPrivate := AClassDef.FFieldOrder[I].IsPrivate; + FieldOrderEntries[I].IsComputed := AClassDef.FFieldOrder[I].IsComputed; + FieldOrderEntries[I].Initializer := AClassDef.FFieldOrder[I].FieldInitializer; + FieldOrderEntries[I].ComputedKey := nil; + if FieldOrderEntries[I].IsComputed and + (AClassDef.FFieldOrder[I].ElementIndex >= 0) and + (AClassDef.FFieldOrder[I].ElementIndex <= High(ResolvedComputedElementKeys)) then + FieldOrderEntries[I].ComputedKey := + ResolvedComputedElementKeys[AClassDef.FFieldOrder[I].ElementIndex]; + end; + ClassValue.SetFieldOrder(FieldOrderEntries); + end; + // TC39 proposal-decorators §3.1 ClassDefinitionEvaluation — auto-accessor setup for I := 0 to High(AClassDef.FElements) do begin if AClassDef.FElements[I].Kind = cekAccessor then begin Elem := AClassDef.FElements[I]; + ComputedKey := nil; AccessorBackingName := '__accessor_' + Elem.Name; + if Elem.IsComputed then + begin + if I <= High(ResolvedComputedElementKeys) then + ComputedKey := ResolvedComputedElementKeys[I]; + if not Assigned(ComputedKey) then + ComputedKey := ToPropertyKey(EvaluateExpression( + Elem.ComputedKeyExpression, AContext)); + AccessorBackingName := '__accessor_computed_' + IntToStr(I); + end; if Assigned(Elem.FieldInitializer) then ClassValue.AddInstanceProperty(AccessorBackingName, Elem.FieldInitializer); - ClassValue.AddAutoAccessor(Elem.Name, AccessorBackingName, Elem.IsStatic); + if Elem.IsComputed then + ClassValue.AddAutoAccessorWithKey( + Elem.Name, ComputedKey, AccessorBackingName, Elem.IsStatic) + else + ClassValue.AddAutoAccessor(Elem.Name, AccessorBackingName, Elem.IsStatic); end; end; @@ -4471,6 +4636,24 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const PropertyValue := TGocciaUndefinedLiteralValue.UndefinedValue; if Elem.IsPrivate then ClassValue.AddPrivateStaticProperty(Elem.Name, PropertyValue) + else if Elem.IsComputed then + begin + ComputedKey := nil; + if I <= High(ResolvedComputedElementKeys) then + ComputedKey := ResolvedComputedElementKeys[I]; + if not Assigned(ComputedKey) then + ComputedKey := ToPropertyKey(EvaluateExpression( + Elem.ComputedKeyExpression, AContext)); + if ComputedKey is TGocciaSymbolValue then + ClassValue.DefineSymbolProperty( + TGocciaSymbolValue(ComputedKey), + TGocciaPropertyDescriptorData.Create(PropertyValue, + [pfEnumerable, pfConfigurable, pfWritable])) + else + ClassValue.DefineProperty(ComputedKey.ToStringLiteral.Value, + TGocciaPropertyDescriptorData.Create(PropertyValue, + [pfEnumerable, pfConfigurable, pfWritable])); + end else ClassValue.DefineProperty(Elem.Name, TGocciaPropertyDescriptorData.Create(PropertyValue, @@ -4545,7 +4728,25 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const cekAccessor: ContextObject.AssignProperty(PROP_KIND, TGocciaStringLiteralValue.Create('accessor')); end; - if Elem.IsPrivate then + ElementName := Elem.Name; + ElementKey := nil; + if (not Elem.IsPrivate) and Elem.IsComputed and + (I <= High(ResolvedComputedElementKeys)) then + begin + ElementKey := ResolvedComputedElementKeys[I]; + if ElementKey is TGocciaSymbolValue then + ContextObject.AssignProperty(PROP_NAME, ElementKey) + else if Assigned(ElementKey) then + begin + ElementName := ElementKey.ToStringLiteral.Value; + ContextObject.AssignProperty(PROP_NAME, + TGocciaStringLiteralValue.Create(ElementName)); + end + else + ContextObject.AssignProperty(PROP_NAME, + TGocciaStringLiteralValue.Create(Elem.Name)); + end + else if Elem.IsPrivate then ContextObject.AssignProperty(PROP_NAME, TGocciaStringLiteralValue.Create('#' + Elem.Name)) else ContextObject.AssignProperty(PROP_NAME, TGocciaStringLiteralValue.Create(Elem.Name)); @@ -4561,7 +4762,6 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const ContextObject.AssignProperty(PROP_METADATA, MetadataObject); // Build access object - ElementName := Elem.Name; AccessObject := TGocciaObjectValue.Create; case Elem.Kind of @@ -4569,20 +4769,64 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const begin if Elem.IsPrivate then CurrentMethod := ClassValue.GetPrivateMethod(ElementName) - else if Elem.IsStatic then - CurrentMethod := ClassValue.GetProperty(ElementName) else - CurrentMethod := ClassValue.Prototype.GetProperty(ElementName); + CurrentMethod := GetDecoratedDataProperty( + Elem.IsStatic, ElementName, ElementKey); - AccessGetterHelper := TGocciaAccessGetter.Create(CurrentMethod, ElementName); + if ElementKey is TGocciaSymbolValue then + AccessGetterHelper := TGocciaAccessGetter.CreateWithKey( + CurrentMethod, ElementKey) + else + AccessGetterHelper := TGocciaAccessGetter.Create( + CurrentMethod, ElementName); AccessObject.AssignProperty(PROP_GET, TGocciaNativeFunctionValue.CreateWithoutPrototype(AccessGetterHelper.Get, PROP_GET, 0)); ContextObject.AssignProperty(PROP_ACCESS, AccessObject); end; + cekGetter: + begin + if not Elem.IsPrivate then + begin + if ElementKey is TGocciaSymbolValue then + AccessGetterHelper := TGocciaAccessGetter.CreateWithKey( + nil, ElementKey) + else + AccessGetterHelper := TGocciaAccessGetter.Create( + nil, ElementName); + AccessObject.AssignProperty(PROP_GET, + TGocciaNativeFunctionValue.CreateWithoutPrototype(AccessGetterHelper.Get, PROP_GET, 0)); + ContextObject.AssignProperty(PROP_ACCESS, AccessObject); + end; + end; + cekSetter: + begin + if not Elem.IsPrivate then + begin + if ElementKey is TGocciaSymbolValue then + AccessSetterHelper := TGocciaAccessSetter.CreateWithKey( + ElementKey) + else + AccessSetterHelper := TGocciaAccessSetter.Create(ElementName); + AccessObject.AssignProperty(PROP_SET, + TGocciaNativeFunctionValue.CreateWithoutPrototype(AccessSetterHelper.SetValue, PROP_SET, 1)); + ContextObject.AssignProperty(PROP_ACCESS, AccessObject); + end; + end; cekField, cekAccessor: begin - AccessGetterHelper := TGocciaAccessGetter.Create(nil, ElementName); - AccessSetterHelper := TGocciaAccessSetter.Create(ElementName); + if ElementKey is TGocciaSymbolValue then + begin + AccessGetterHelper := TGocciaAccessGetter.CreateWithKey( + nil, ElementKey); + AccessSetterHelper := TGocciaAccessSetter.CreateWithKey( + ElementKey); + end + else + begin + AccessGetterHelper := TGocciaAccessGetter.Create( + nil, ElementName); + AccessSetterHelper := TGocciaAccessSetter.Create(ElementName); + end; AccessObject.AssignProperty(PROP_GET, TGocciaNativeFunctionValue.CreateWithoutPrototype(AccessGetterHelper.Get, PROP_GET, 0)); AccessObject.AssignProperty(PROP_SET, @@ -4610,10 +4854,9 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const begin if Elem.IsPrivate then DecoratorArgs.Add(ClassValue.GetPrivateMethod(ElementName)) - else if Elem.IsStatic then - DecoratorArgs.Add(ClassValue.GetProperty(ElementName)) else - DecoratorArgs.Add(ClassValue.Prototype.GetProperty(ElementName)); + DecoratorArgs.Add(GetDecoratedDataProperty( + Elem.IsStatic, ElementName, ElementKey)); DecoratorArgs.Add(ContextObject); DecoratorResult := TGocciaFunctionBase(DecoratorFn).Call(DecoratorArgs, TGocciaUndefinedLiteralValue.UndefinedValue); @@ -4628,12 +4871,9 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const if DecoratorResult is TGocciaMethodValue then ClassValue.AddPrivateMethod(ElementName, TGocciaMethodValue(DecoratorResult)); end - else if Elem.IsStatic then - ClassValue.DefineProperty(ElementName, - TGocciaPropertyDescriptorData.Create( - DecoratorResult, [pfConfigurable, pfWritable])) else - ClassValue.Prototype.AssignProperty(ElementName, DecoratorResult); + DefineDecoratedDataProperty( + Elem.IsStatic, ElementName, ElementKey, DecoratorResult); end; end; @@ -4641,10 +4881,16 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const begin if Elem.IsPrivate then GetterFnValue := ClassValue.PrivatePropertyGetter[ElementName] - else if Elem.IsStatic then - GetterFnValue := ClassValue.StaticPropertyGetter[ElementName] else - GetterFnValue := ClassValue.PropertyGetter[ElementName]; + begin + ExistingDescriptor := GetDecoratedAccessorDescriptor( + Elem.IsStatic, ElementName, ElementKey); + GetterFnValue := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter) then + GetterFnValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter; + end; DecoratorArgs.Add(GetterFnValue); DecoratorArgs.Add(ContextObject); @@ -4658,10 +4904,19 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const if Elem.IsPrivate then ClassValue.AddPrivateGetter(ElementName, TGocciaFunctionValue(DecoratorResult)) - else if Elem.IsStatic then - ClassValue.AddStaticGetter(ElementName, TGocciaFunctionValue(DecoratorResult)) else - ClassValue.AddGetter(ElementName, TGocciaFunctionValue(DecoratorResult)); + begin + ExistingDescriptor := GetDecoratedAccessorDescriptor( + Elem.IsStatic, ElementName, ElementKey); + ExistingSetterValue := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter) then + ExistingSetterValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter; + DefineDecoratedAccessorProperty( + Elem.IsStatic, ElementName, ElementKey, + DecoratorResult, ExistingSetterValue); + end; end; end; @@ -4669,10 +4924,16 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const begin if Elem.IsPrivate then SetterFnValue := ClassValue.PrivatePropertySetter[ElementName] - else if Elem.IsStatic then - SetterFnValue := ClassValue.StaticPropertySetter[ElementName] else - SetterFnValue := ClassValue.PropertySetter[ElementName]; + begin + ExistingDescriptor := GetDecoratedAccessorDescriptor( + Elem.IsStatic, ElementName, ElementKey); + SetterFnValue := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter) then + SetterFnValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter; + end; DecoratorArgs.Add(SetterFnValue); DecoratorArgs.Add(ContextObject); @@ -4686,10 +4947,19 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const if Elem.IsPrivate then ClassValue.AddPrivateSetter(ElementName, TGocciaFunctionValue(DecoratorResult)) - else if Elem.IsStatic then - ClassValue.AddStaticSetter(ElementName, TGocciaFunctionValue(DecoratorResult)) else - ClassValue.AddSetter(ElementName, TGocciaFunctionValue(DecoratorResult)); + begin + ExistingDescriptor := GetDecoratedAccessorDescriptor( + Elem.IsStatic, ElementName, ElementKey); + ExistingGetterValue := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter) then + ExistingGetterValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter; + DefineDecoratedAccessorProperty( + Elem.IsStatic, ElementName, ElementKey, + ExistingGetterValue, DecoratorResult); + end; end; end; @@ -4704,15 +4974,26 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const begin if not DecoratorResult.IsCallable then AContext.OnError('Field decorator must return a function or undefined', ALine, AColumn); - ClassValue.AddFieldInitializer(ElementName, DecoratorResult, Elem.IsPrivate, Elem.IsStatic); + ClassValue.AddFieldInitializerWithKey(ElementName, ElementKey, DecoratorResult, Elem.IsPrivate, Elem.IsStatic); end; end; cekAccessor: begin AutoAccessorValue := TGocciaObjectValue.Create; - AutoAccessorValue.AssignProperty(PROP_GET, ClassValue.PropertyGetter[ElementName]); - AutoAccessorValue.AssignProperty(PROP_SET, ClassValue.PropertySetter[ElementName]); + ExistingDescriptor := GetDecoratedAccessorDescriptor( + Elem.IsStatic, ElementName, ElementKey); + ExistingGetterValue := nil; + ExistingSetterValue := nil; + if ExistingDescriptor is TGocciaPropertyDescriptorAccessor then + begin + ExistingGetterValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter; + ExistingSetterValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter; + end; + AutoAccessorValue.AssignProperty(PROP_GET, ExistingGetterValue); + AutoAccessorValue.AssignProperty(PROP_SET, ExistingSetterValue); DecoratorArgs.Add(AutoAccessorValue); DecoratorArgs.Add(ContextObject); @@ -4729,11 +5010,16 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const NewInit := DecResultObj.GetProperty(PROP_INIT); if Assigned(NewGetter) and not (NewGetter is TGocciaUndefinedLiteralValue) then - ClassValue.AddGetter(ElementName, TGocciaFunctionValue(NewGetter)); + ExistingGetterValue := NewGetter; if Assigned(NewSetter) and not (NewSetter is TGocciaUndefinedLiteralValue) then - ClassValue.AddSetter(ElementName, TGocciaFunctionValue(NewSetter)); + ExistingSetterValue := NewSetter; + if (Assigned(NewGetter) and not (NewGetter is TGocciaUndefinedLiteralValue)) or + (Assigned(NewSetter) and not (NewSetter is TGocciaUndefinedLiteralValue)) then + DefineDecoratedAccessorProperty( + Elem.IsStatic, ElementName, ElementKey, + ExistingGetterValue, ExistingSetterValue); if Assigned(NewInit) and not (NewInit is TGocciaUndefinedLiteralValue) and NewInit.IsCallable then - ClassValue.AddFieldInitializer(ElementName, NewInit, Elem.IsPrivate, Elem.IsStatic); + ClassValue.AddFieldInitializerWithKey(ElementName, ElementKey, NewInit, Elem.IsPrivate, Elem.IsStatic); end; end; end; @@ -4743,6 +5029,9 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const end; end; + OriginalClassValue := ClassValue; + OriginalClassValue.RunDecoratorStaticFieldInitializers; + // Phase 3: Call class decorators (bottom-up) for I := High(EvaluatedClassDecorators) downto 0 do begin @@ -4819,6 +5108,13 @@ function EvaluateClassDefinition(const AClassDef: TGocciaClassDefinition; const end; end; + if Assigned(Continuation) then + for I := 0 to High(AClassDef.FElements) do + if AClassDef.FElements[I].IsComputed and + (AClassDef.FElements[I].Kind in [cekMethod, cekGetter, cekSetter, cekField, cekAccessor]) then + Continuation.ClearCompletedExpressionValue( + AClassDef.FElements[I].ComputedKeyExpression); + Result := ClassValue; end; diff --git a/source/units/Goccia.Generator.Continuation.pas b/source/units/Goccia.Generator.Continuation.pas index 7f489507..dbacbb15 100644 --- a/source/units/Goccia.Generator.Continuation.pas +++ b/source/units/Goccia.Generator.Continuation.pas @@ -121,6 +121,7 @@ TGocciaGeneratorContinuation = class const AContext: TGocciaEvaluationContext): TGocciaValue; procedure SaveCompletedExpressionValue(const AExpression: TObject; const AValue: TGocciaValue); function TakeCompletedExpressionValue(const AExpression: TObject; out AValue: TGocciaValue): Boolean; + procedure ClearCompletedExpressionValue(const AExpression: TObject); procedure SaveExpressionValue(const AExpression: TObject; const AValue: TGocciaValue); function TakeExpressionValue(const AExpression: TObject; out AValue: TGocciaValue): Boolean; procedure ClearExpressionValue(const AExpression: TObject); @@ -966,6 +967,12 @@ function TGocciaGeneratorContinuation.TakeCompletedExpressionValue( FCompletedExpressionValues.Remove(AExpression); end; +procedure TGocciaGeneratorContinuation.ClearCompletedExpressionValue( + const AExpression: TObject); +begin + FCompletedExpressionValues.Remove(AExpression); +end; + procedure TGocciaGeneratorContinuation.SaveExpressionValue( const AExpression: TObject; const AValue: TGocciaValue); begin diff --git a/source/units/Goccia.Parser.pas b/source/units/Goccia.Parser.pas index a480f387..22031914 100644 --- a/source/units/Goccia.Parser.pas +++ b/source/units/Goccia.Parser.pas @@ -1998,7 +1998,7 @@ function TGocciaParser.Primary: TGocciaExpression; begin AsyncStartLine := Token.Line; AsyncStartColumn := Token.Column; - case TryConsumeAsyncFunction(Token.Line, Token.Column) of + case TryConsumeAsyncFunction(AsyncStartLine, AsyncStartColumn) of afcNotMatched: Result := TGocciaIdentifierExpression.Create(Name, Token.Line, Token.Column); afcDisabled: @@ -5551,8 +5551,8 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef Elements[High(Elements)].Name := MemberName; Elements[High(Elements)].IsStatic := IsStatic; Elements[High(Elements)].IsPrivate := IsPrivate; - Elements[High(Elements)].IsComputed := False; - Elements[High(Elements)].ComputedKeyExpression := nil; + Elements[High(Elements)].IsComputed := IsComputed; + Elements[High(Elements)].ComputedKeyExpression := ComputedKeyExpression; Elements[High(Elements)].Decorators := MemberDecorators; Elements[High(Elements)].FieldInitializer := PropertyValue; Elements[High(Elements)].TypeAnnotation := FieldType; @@ -5565,21 +5565,34 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef ConsumeSemicolonOrASI('Expected ";" after property', SSuggestAddSemicolon); - if (Length(MemberDecorators) > 0) or IsStatic then + if (Length(MemberDecorators) > 0) or IsStatic or IsComputed then begin SetLength(Elements, Length(Elements) + 1); Elements[High(Elements)].Kind := cekField; Elements[High(Elements)].Name := MemberName; Elements[High(Elements)].IsStatic := IsStatic; Elements[High(Elements)].IsPrivate := IsPrivate; - Elements[High(Elements)].IsComputed := False; - Elements[High(Elements)].ComputedKeyExpression := nil; + Elements[High(Elements)].IsComputed := IsComputed; + Elements[High(Elements)].ComputedKeyExpression := ComputedKeyExpression; Elements[High(Elements)].Decorators := MemberDecorators; Elements[High(Elements)].FieldInitializer := PropertyValue; Elements[High(Elements)].TypeAnnotation := FieldType; end; - if IsPrivate and IsStatic then + if IsComputed then + begin + if not IsStatic then + begin + SetLength(FieldOrder, Length(FieldOrder) + 1); + FieldOrder[High(FieldOrder)].Name := ''; + FieldOrder[High(FieldOrder)].IsPrivate := False; + FieldOrder[High(FieldOrder)].IsComputed := True; + FieldOrder[High(FieldOrder)].ElementIndex := High(Elements); + FieldOrder[High(FieldOrder)].ComputedKeyExpression := ComputedKeyExpression; + FieldOrder[High(FieldOrder)].FieldInitializer := PropertyValue; + end; + end + else if IsPrivate and IsStatic then PrivateStaticProperties.Add(MemberName, PropertyValue) else if IsPrivate then begin @@ -5587,6 +5600,10 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef SetLength(FieldOrder, Length(FieldOrder) + 1); FieldOrder[High(FieldOrder)].Name := MemberName; FieldOrder[High(FieldOrder)].IsPrivate := True; + FieldOrder[High(FieldOrder)].IsComputed := False; + FieldOrder[High(FieldOrder)].ElementIndex := -1; + FieldOrder[High(FieldOrder)].ComputedKeyExpression := nil; + FieldOrder[High(FieldOrder)].FieldInitializer := nil; end else if IsStatic then StaticProperties.Add(MemberName, PropertyValue) @@ -5596,6 +5613,10 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef SetLength(FieldOrder, Length(FieldOrder) + 1); FieldOrder[High(FieldOrder)].Name := MemberName; FieldOrder[High(FieldOrder)].IsPrivate := False; + FieldOrder[High(FieldOrder)].IsComputed := False; + FieldOrder[High(FieldOrder)].ElementIndex := -1; + FieldOrder[High(FieldOrder)].ComputedKeyExpression := nil; + FieldOrder[High(FieldOrder)].FieldInitializer := nil; if FieldType <> '' then InstancePropertyTypes.Add(MemberName, FieldType); end; @@ -5606,21 +5627,34 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef Advance; PropertyValue := TGocciaLiteralExpression.Create(TGocciaUndefinedLiteralValue.UndefinedValue, Peek.Line, Peek.Column); - if (Length(MemberDecorators) > 0) or IsStatic then + if (Length(MemberDecorators) > 0) or IsStatic or IsComputed then begin SetLength(Elements, Length(Elements) + 1); Elements[High(Elements)].Kind := cekField; Elements[High(Elements)].Name := MemberName; Elements[High(Elements)].IsStatic := IsStatic; Elements[High(Elements)].IsPrivate := IsPrivate; - Elements[High(Elements)].IsComputed := False; - Elements[High(Elements)].ComputedKeyExpression := nil; + Elements[High(Elements)].IsComputed := IsComputed; + Elements[High(Elements)].ComputedKeyExpression := ComputedKeyExpression; Elements[High(Elements)].Decorators := MemberDecorators; Elements[High(Elements)].FieldInitializer := PropertyValue; Elements[High(Elements)].TypeAnnotation := FieldType; end; - if IsPrivate and IsStatic then + if IsComputed then + begin + if not IsStatic then + begin + SetLength(FieldOrder, Length(FieldOrder) + 1); + FieldOrder[High(FieldOrder)].Name := ''; + FieldOrder[High(FieldOrder)].IsPrivate := False; + FieldOrder[High(FieldOrder)].IsComputed := True; + FieldOrder[High(FieldOrder)].ElementIndex := High(Elements); + FieldOrder[High(FieldOrder)].ComputedKeyExpression := ComputedKeyExpression; + FieldOrder[High(FieldOrder)].FieldInitializer := PropertyValue; + end; + end + else if IsPrivate and IsStatic then PrivateStaticProperties.Add(MemberName, PropertyValue) else if IsPrivate then begin @@ -5628,6 +5662,10 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef SetLength(FieldOrder, Length(FieldOrder) + 1); FieldOrder[High(FieldOrder)].Name := MemberName; FieldOrder[High(FieldOrder)].IsPrivate := True; + FieldOrder[High(FieldOrder)].IsComputed := False; + FieldOrder[High(FieldOrder)].ElementIndex := -1; + FieldOrder[High(FieldOrder)].ComputedKeyExpression := nil; + FieldOrder[High(FieldOrder)].FieldInitializer := nil; end else if IsStatic then StaticProperties.Add(MemberName, PropertyValue) @@ -5637,6 +5675,10 @@ function TGocciaParser.ParseClassBody(const AClassName: string): TGocciaClassDef SetLength(FieldOrder, Length(FieldOrder) + 1); FieldOrder[High(FieldOrder)].Name := MemberName; FieldOrder[High(FieldOrder)].IsPrivate := False; + FieldOrder[High(FieldOrder)].IsComputed := False; + FieldOrder[High(FieldOrder)].ElementIndex := -1; + FieldOrder[High(FieldOrder)].ComputedKeyExpression := nil; + FieldOrder[High(FieldOrder)].FieldInitializer := nil; if FieldType <> '' then InstancePropertyTypes.Add(MemberName, FieldType); end; diff --git a/source/units/Goccia.VM.pas b/source/units/Goccia.VM.pas index 8dfa559e..93c48c02 100644 --- a/source/units/Goccia.VM.pas +++ b/source/units/Goccia.VM.pas @@ -184,7 +184,9 @@ TGocciaVM = class function FinalizeEnumValue(const AValue: TGocciaValue; const AName: string): TGocciaValue; procedure StampBytecodePrivateBrands(const AClassValue: TGocciaClassValue; const AInstance: TGocciaValue); - procedure SetupAutoAccessorValue(const AName: string); + procedure SetupAutoAccessorValue(const AName: string; const AIsStatic: Boolean); + procedure SetupAutoAccessorValueByKey(const AKey: TGocciaValue; + const ABackingName: string; const AIsStatic: Boolean); procedure RunClassInitializers(const AClassValue: TGocciaClassValue; const AInstance: TGocciaValue); function MaterializeArguments( @@ -197,7 +199,7 @@ TGocciaVM = class const AArguments: TGocciaRegisterArray): TGocciaValue; procedure BeginDecorators(const AClassValue, ASuperValue: TGocciaValue); procedure ApplyElementDecorator(const ADecoratorFn: TGocciaValue; - const ADescriptor: string); + const ADescriptor: string; const AComputedKey: TGocciaValue = nil); procedure ApplyClassDecorator(const ADecoratorFn: TGocciaValue); function FinishDecorators(const ACurrentValue: TGocciaValue): TGocciaValue; function GetSuperPropertyValue(const ASuperValue, AThisValue: TGocciaValue; @@ -772,6 +774,8 @@ TGocciaVMDecoratorSession = class StaticFieldCollector: TGocciaInitializerCollector; ClassCollector: TGocciaInitializerCollector; ClassValue: TGocciaValue; + OriginalClassValue: TGocciaValue; + StaticDecoratorInitializersRun: Boolean; constructor Create(const AMetadataObject: TGocciaObjectValue); destructor Destroy; override; procedure MarkReferences; @@ -1216,6 +1220,8 @@ constructor TGocciaVMDecoratorSession.Create( StaticFieldCollector := TGocciaInitializerCollector.Create; ClassCollector := TGocciaInitializerCollector.Create; ClassValue := nil; + OriginalClassValue := nil; + StaticDecoratorInitializersRun := False; end; destructor TGocciaVMDecoratorSession.Destroy; @@ -1247,12 +1253,26 @@ procedure TGocciaVMDecoratorSession.MarkReferences; MetadataObject.MarkReferences; if Assigned(ClassValue) then ClassValue.MarkReferences; + if Assigned(OriginalClassValue) and (OriginalClassValue <> ClassValue) then + OriginalClassValue.MarkReferences; MarkCollector(MethodCollector); MarkCollector(FieldCollector); MarkCollector(StaticFieldCollector); MarkCollector(ClassCollector); end; +procedure RunStaticDecoratorInitializersForSession( + const ASession: TGocciaVMDecoratorSession); +begin + if not Assigned(ASession) or ASession.StaticDecoratorInitializersRun then + Exit; + + if ASession.OriginalClassValue is TGocciaClassValue then + TGocciaClassValue(ASession.OriginalClassValue) + .RunDecoratorStaticFieldInitializers; + ASession.StaticDecoratorInitializersRun := True; +end; + threadvar GActiveBytecodeGenerator: TGocciaBytecodeGeneratorObjectValue; @@ -5848,7 +5868,8 @@ function TGocciaVM.InvokeImplicitSuperInitializationRegisters( Result := TargetInstance; end; -procedure TGocciaVM.SetupAutoAccessorValue(const AName: string); +procedure TGocciaVM.SetupAutoAccessorValue(const AName: string; + const AIsStatic: Boolean); var ClassVal: TGocciaClassValue; begin @@ -5859,7 +5880,22 @@ procedure TGocciaVM.SetupAutoAccessorValue(const AName: string); ClassVal := TGocciaClassValue( TGocciaVMDecoratorSession(FActiveDecoratorSession).ClassValue); - ClassVal.AddAutoAccessor(AName, '__accessor_' + AName, False); + ClassVal.AddAutoAccessor(AName, '__accessor_' + AName, AIsStatic); +end; + +procedure TGocciaVM.SetupAutoAccessorValueByKey(const AKey: TGocciaValue; + const ABackingName: string; const AIsStatic: Boolean); +var + ClassVal: TGocciaClassValue; +begin + if not Assigned(FActiveDecoratorSession) then + Exit; + if not (TGocciaVMDecoratorSession(FActiveDecoratorSession).ClassValue is TGocciaClassValue) then + Exit; + + ClassVal := TGocciaClassValue( + TGocciaVMDecoratorSession(FActiveDecoratorSession).ClassValue); + ClassVal.AddAutoAccessorWithKey('', AKey, ABackingName, AIsStatic); end; procedure TGocciaVM.BeginDecorators(const AClassValue, ASuperValue: TGocciaValue); @@ -5897,6 +5933,8 @@ procedure TGocciaVM.BeginDecorators(const AClassValue, ASuperValue: TGocciaValue FActiveDecoratorSession := TGocciaVMDecoratorSession.Create(Meta); TGocciaVMDecoratorSession(FActiveDecoratorSession).ClassValue := ClassVal; + TGocciaVMDecoratorSession(FActiveDecoratorSession).OriginalClassValue := + ClassVal; end; procedure TGocciaVM.ApplyClassDecorator(const ADecoratorFn: TGocciaValue); @@ -5915,6 +5953,7 @@ procedure TGocciaVM.ApplyClassDecorator(const ADecoratorFn: TGocciaValue); Exit; if not ADecoratorFn.IsCallable then ThrowTypeError(SErrorDecoratorMustBeFunction, SSuggestDecoratorFunction); + RunStaticDecoratorInitializersForSession(Session); ContextObject := TGocciaObjectValue.Create; ContextObject.AssignProperty(PROP_KIND, @@ -5946,11 +5985,13 @@ procedure TGocciaVM.ApplyClassDecorator(const ADecoratorFn: TGocciaValue); end; procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; - const ADescriptor: string); + const ADescriptor: string; const AComputedKey: TGocciaValue = nil); var Session: TGocciaVMDecoratorSession; Kind: Char; Name: string; + ElementName: string; + ElementKey: TGocciaValue; Flags: Integer; IsStatic, IsPrivate: Boolean; ClassVal, DecoratorResult, ElementValue: TGocciaValue; @@ -5962,6 +6003,175 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; KindStr: string; ExistingDescriptor: TGocciaPropertyDescriptor; GetterValue, SetterValue, NewGetter, NewSetter, NewInit: TGocciaValue; + + function GetDecoratedDataProperty(const AIsStatic: Boolean; + const AName: string; const AKey: TGocciaValue): TGocciaValue; + begin + if AKey is TGocciaSymbolValue then + begin + if AIsStatic then + Result := TGocciaClassValue(ClassVal).GetSymbolProperty( + TGocciaSymbolValue(AKey)) + else + Result := TGocciaClassValue(ClassVal).Prototype.GetSymbolProperty( + TGocciaSymbolValue(AKey)); + end + else if AIsStatic then + Result := TGocciaClassValue(ClassVal).GetProperty(AName) + else + Result := TGocciaClassValue(ClassVal).Prototype.GetProperty(AName); + end; + + procedure DefineDecoratedMethodProperty(const AIsStatic: Boolean; + const AName: string; const AKey, AValue: TGocciaValue); + var + TargetObject: TGocciaObjectValue; + KeyValue: TGocciaValue; + begin + if AIsStatic then + TargetObject := TGocciaClassValue(ClassVal) + else + TargetObject := TGocciaClassValue(ClassVal).Prototype; + if Assigned(AKey) then + KeyValue := AKey + else + KeyValue := TGocciaStringLiteralValue.Create(AName); + if AIsStatic then + SetBytecodeHomeObject(AValue, TGocciaClassValue(ClassVal)) + else + SetBytecodeHomeObject(AValue, TargetObject); + if KeyValue is TGocciaSymbolValue then + TargetObject.DefineSymbolProperty( + TGocciaSymbolValue(KeyValue), + TGocciaPropertyDescriptorData.Create( + AValue, [pfConfigurable, pfWritable])) + else + TargetObject.DefineProperty( + KeyValue.ToStringLiteral.Value, + TGocciaPropertyDescriptorData.Create( + AValue, [pfConfigurable, pfWritable])); + end; + + function GetDecoratedAccessorDescriptor(const AIsStatic: Boolean; + const AName: string; const AKey: TGocciaValue): TGocciaPropertyDescriptor; + begin + if AKey is TGocciaSymbolValue then + begin + if AIsStatic then + Result := TGocciaClassValue(ClassVal).GetOwnStaticSymbolDescriptor( + TGocciaSymbolValue(AKey)) + else + Result := TGocciaClassValue(ClassVal).Prototype + .GetOwnSymbolPropertyDescriptor(TGocciaSymbolValue(AKey)); + end + else if AIsStatic then + Result := TGocciaClassValue(ClassVal).GetOwnPropertyDescriptor(AName) + else + Result := TGocciaClassValue(ClassVal).Prototype + .GetOwnPropertyDescriptor(AName); + end; + + procedure DefineDecoratedGetterProperty(const AIsStatic: Boolean; + const AName: string; const AKey, AGetter: TGocciaValue); + var + TargetObject: TGocciaObjectValue; + KeyValue: TGocciaValue; + ExistingDescriptor: TGocciaPropertyDescriptor; + ExistingSetter: TGocciaValue; + begin + if AIsStatic then + TargetObject := TGocciaClassValue(ClassVal) + else + TargetObject := TGocciaClassValue(ClassVal).Prototype; + if Assigned(AKey) then + KeyValue := AKey + else + KeyValue := TGocciaStringLiteralValue.Create(AName); + + if AIsStatic then + SetBytecodeHomeObject(AGetter, TGocciaClassValue(ClassVal)) + else + SetBytecodeHomeObject(AGetter, TargetObject); + if KeyValue is TGocciaSymbolValue then + begin + if AIsStatic then + ExistingDescriptor := TGocciaClassValue(ClassVal) + .GetOwnStaticSymbolDescriptor(TGocciaSymbolValue(KeyValue)) + else + ExistingDescriptor := TargetObject.GetOwnSymbolPropertyDescriptor( + TGocciaSymbolValue(KeyValue)); + end + else + ExistingDescriptor := TargetObject.GetOwnPropertyDescriptor( + KeyValue.ToStringLiteral.Value); + + ExistingSetter := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter) then + ExistingSetter := TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter; + + if KeyValue is TGocciaSymbolValue then + TargetObject.DefineSymbolProperty( + TGocciaSymbolValue(KeyValue), + TGocciaPropertyDescriptorAccessor.Create( + AGetter, ExistingSetter, [pfConfigurable])) + else + TargetObject.DefineProperty( + KeyValue.ToStringLiteral.Value, + TGocciaPropertyDescriptorAccessor.Create( + AGetter, ExistingSetter, [pfConfigurable])); + end; + + procedure DefineDecoratedSetterProperty(const AIsStatic: Boolean; + const AName: string; const AKey, ASetter: TGocciaValue); + var + TargetObject: TGocciaObjectValue; + KeyValue: TGocciaValue; + ExistingDescriptor: TGocciaPropertyDescriptor; + ExistingGetter: TGocciaValue; + begin + if AIsStatic then + TargetObject := TGocciaClassValue(ClassVal) + else + TargetObject := TGocciaClassValue(ClassVal).Prototype; + if Assigned(AKey) then + KeyValue := AKey + else + KeyValue := TGocciaStringLiteralValue.Create(AName); + + if AIsStatic then + SetBytecodeHomeObject(ASetter, TGocciaClassValue(ClassVal)) + else + SetBytecodeHomeObject(ASetter, TargetObject); + if KeyValue is TGocciaSymbolValue then + begin + if AIsStatic then + ExistingDescriptor := TGocciaClassValue(ClassVal) + .GetOwnStaticSymbolDescriptor(TGocciaSymbolValue(KeyValue)) + else + ExistingDescriptor := TargetObject.GetOwnSymbolPropertyDescriptor( + TGocciaSymbolValue(KeyValue)); + end + else + ExistingDescriptor := TargetObject.GetOwnPropertyDescriptor( + KeyValue.ToStringLiteral.Value); + + ExistingGetter := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter) then + ExistingGetter := TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter; + + if KeyValue is TGocciaSymbolValue then + TargetObject.DefineSymbolProperty( + TGocciaSymbolValue(KeyValue), + TGocciaPropertyDescriptorAccessor.Create( + ExistingGetter, ASetter, [pfConfigurable])) + else + TargetObject.DefineProperty( + KeyValue.ToStringLiteral.Value, + TGocciaPropertyDescriptorAccessor.Create( + ExistingGetter, ASetter, [pfConfigurable])); + end; begin if not Assigned(FActiveDecoratorSession) then Exit; @@ -5976,6 +6186,15 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; ParseElementDescriptor(ADescriptor, Kind, Name, Flags); IsStatic := (Flags and 1) <> 0; IsPrivate := (Flags and 2) <> 0; + ElementName := Name; + ElementKey := nil; + if (not IsPrivate) and Assigned(AComputedKey) and + not (AComputedKey is TGocciaUndefinedLiteralValue) then + begin + ElementKey := AComputedKey; + if not (ElementKey is TGocciaSymbolValue) then + ElementName := ElementKey.ToStringLiteral.Value; + end; case Kind of 'm': KindStr := 'method'; @@ -5993,6 +6212,11 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; if IsPrivate then ContextObject.AssignProperty(PROP_NAME, TGocciaStringLiteralValue.Create('#' + Name)) + else if ElementKey is TGocciaSymbolValue then + ContextObject.AssignProperty(PROP_NAME, ElementKey) + else if Assigned(ElementKey) then + ContextObject.AssignProperty(PROP_NAME, + TGocciaStringLiteralValue.Create(ElementName)) else ContextObject.AssignProperty(PROP_NAME, TGocciaStringLiteralValue.Create(Name)); @@ -6016,20 +6240,64 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; begin if IsPrivate then ElementValue := TGocciaClassValue(ClassVal).GetPrivateMethod(Name) - else if IsStatic then - ElementValue := TGocciaClassValue(ClassVal).GetProperty(Name) else - ElementValue := TGocciaClassValue(ClassVal).Prototype.GetProperty(Name); - AccessGetterHelper := TGocciaAccessGetter.Create(ElementValue, Name); + ElementValue := GetDecoratedDataProperty( + IsStatic, ElementName, ElementKey); + if ElementKey is TGocciaSymbolValue then + AccessGetterHelper := TGocciaAccessGetter.CreateWithKey( + ElementValue, ElementKey) + else + AccessGetterHelper := TGocciaAccessGetter.Create( + ElementValue, ElementName); AccessObject.AssignProperty(PROP_GET, TGocciaNativeFunctionValue.CreateWithoutPrototype( AccessGetterHelper.Get, PROP_GET, 0)); ContextObject.AssignProperty(PROP_ACCESS, AccessObject); end; + 'g': + begin + if not IsPrivate then + begin + if ElementKey is TGocciaSymbolValue then + AccessGetterHelper := TGocciaAccessGetter.CreateWithKey( + nil, ElementKey) + else + AccessGetterHelper := TGocciaAccessGetter.Create(nil, ElementName); + AccessObject.AssignProperty(PROP_GET, + TGocciaNativeFunctionValue.CreateWithoutPrototype( + AccessGetterHelper.Get, PROP_GET, 0)); + ContextObject.AssignProperty(PROP_ACCESS, AccessObject); + end; + end; + 's': + begin + if not IsPrivate then + begin + if ElementKey is TGocciaSymbolValue then + AccessSetterHelper := TGocciaAccessSetter.CreateWithKey( + ElementKey) + else + AccessSetterHelper := TGocciaAccessSetter.Create(ElementName); + AccessObject.AssignProperty(PROP_SET, + TGocciaNativeFunctionValue.CreateWithoutPrototype( + AccessSetterHelper.SetValue, PROP_SET, 1)); + ContextObject.AssignProperty(PROP_ACCESS, AccessObject); + end; + end; 'f', 'a': begin - AccessGetterHelper := TGocciaAccessGetter.Create(nil, Name); - AccessSetterHelper := TGocciaAccessSetter.Create(Name); + if ElementKey is TGocciaSymbolValue then + begin + AccessGetterHelper := TGocciaAccessGetter.CreateWithKey( + nil, ElementKey); + AccessSetterHelper := TGocciaAccessSetter.CreateWithKey( + ElementKey); + end + else + begin + AccessGetterHelper := TGocciaAccessGetter.Create(nil, ElementName); + AccessSetterHelper := TGocciaAccessSetter.Create(ElementName); + end; AccessObject.AssignProperty(PROP_GET, TGocciaNativeFunctionValue.CreateWithoutPrototype( AccessGetterHelper.Get, PROP_GET, 0)); @@ -6057,39 +6325,50 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; begin if IsPrivate then ElementValue := TGocciaClassValue(ClassVal).PrivatePropertyGetter[Name] - else if IsStatic then - ElementValue := TGocciaClassValue(ClassVal).StaticPropertyGetter[Name] else - ElementValue := TGocciaClassValue(ClassVal).PropertyGetter[Name]; + begin + ExistingDescriptor := GetDecoratedAccessorDescriptor( + IsStatic, ElementName, ElementKey); + ElementValue := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter) then + ElementValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter; + end; end; 's': begin if IsPrivate then ElementValue := TGocciaClassValue(ClassVal).PrivatePropertySetter[Name] - else if IsStatic then - ElementValue := TGocciaClassValue(ClassVal).StaticPropertySetter[Name] else - ElementValue := TGocciaClassValue(ClassVal).PropertySetter[Name]; + begin + ExistingDescriptor := GetDecoratedAccessorDescriptor( + IsStatic, ElementName, ElementKey); + ElementValue := nil; + if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and + Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter) then + ElementValue := + TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter; + end; end; 'f': ElementValue := TGocciaUndefinedLiteralValue.UndefinedValue; 'a': begin AutoAccessorValue := TGocciaObjectValue.Create; - if IsStatic then + ExistingDescriptor := GetDecoratedAccessorDescriptor( + IsStatic, ElementName, ElementKey); + GetterValue := nil; + SetterValue := nil; + if ExistingDescriptor is TGocciaPropertyDescriptorAccessor then begin - AutoAccessorValue.AssignProperty(PROP_GET, - TGocciaClassValue(ClassVal).StaticPropertyGetter[Name]); - AutoAccessorValue.AssignProperty(PROP_SET, - TGocciaClassValue(ClassVal).StaticPropertySetter[Name]); - end - else - begin - AutoAccessorValue.AssignProperty(PROP_GET, - TGocciaClassValue(ClassVal).PropertyGetter[Name]); - AutoAccessorValue.AssignProperty(PROP_SET, - TGocciaClassValue(ClassVal).PropertySetter[Name]); + GetterValue := TGocciaPropertyDescriptorAccessor( + ExistingDescriptor).Getter; + SetterValue := TGocciaPropertyDescriptorAccessor( + ExistingDescriptor).Setter; end; + AutoAccessorValue.AssignProperty(PROP_GET, GetterValue); + AutoAccessorValue.AssignProperty(PROP_SET, SetterValue); ElementValue := AutoAccessorValue; end; end; @@ -6116,58 +6395,38 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; if IsPrivate then TGocciaClassValue(ClassVal).Prototype.AssignProperty( '#' + Name, DecoratorResult) - else if IsStatic then - TGocciaClassValue(ClassVal).SetProperty(Name, DecoratorResult) else - TGocciaClassValue(ClassVal).Prototype.AssignProperty( - Name, DecoratorResult); + DefineDecoratedMethodProperty( + IsStatic, ElementName, ElementKey, DecoratorResult); end; 'g': begin if not DecoratorResult.IsCallable then ThrowTypeError(SErrorGetterDecoratorReturn, SSuggestDecoratorFunction); - if IsStatic then - TGocciaClassValue(ClassVal).AddStaticGetter( - Name, TGocciaFunctionValue(DecoratorResult)) + if IsPrivate then + TGocciaClassValue(ClassVal).AddPrivateGetter( + Name, TGocciaFunctionBase(DecoratorResult)) else - begin - ExistingDescriptor := TGocciaClassValue(ClassVal).Prototype - .GetOwnPropertyDescriptor(Name); - SetterValue := nil; - if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and - Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter) then - SetterValue := TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Setter; - TGocciaClassValue(ClassVal).Prototype.DefineProperty(Name, - TGocciaPropertyDescriptorAccessor.Create( - DecoratorResult, SetterValue, [pfEnumerable, pfConfigurable, pfWritable])); - end; + DefineDecoratedGetterProperty( + IsStatic, ElementName, ElementKey, DecoratorResult); end; 's': begin if not DecoratorResult.IsCallable then ThrowTypeError(SErrorSetterDecoratorReturn, SSuggestDecoratorFunction); - if IsStatic then - TGocciaClassValue(ClassVal).AddStaticSetter( - Name, TGocciaFunctionValue(DecoratorResult)) + if IsPrivate then + TGocciaClassValue(ClassVal).AddPrivateSetter( + Name, TGocciaFunctionBase(DecoratorResult)) else - begin - ExistingDescriptor := TGocciaClassValue(ClassVal).Prototype - .GetOwnPropertyDescriptor(Name); - GetterValue := nil; - if (ExistingDescriptor is TGocciaPropertyDescriptorAccessor) and - Assigned(TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter) then - GetterValue := TGocciaPropertyDescriptorAccessor(ExistingDescriptor).Getter; - TGocciaClassValue(ClassVal).Prototype.DefineProperty(Name, - TGocciaPropertyDescriptorAccessor.Create( - GetterValue, DecoratorResult, [pfEnumerable, pfConfigurable, pfWritable])); - end; + DefineDecoratedSetterProperty( + IsStatic, ElementName, ElementKey, DecoratorResult); end; 'f': begin if not DecoratorResult.IsCallable then ThrowTypeError(SErrorFieldDecoratorReturn, SSuggestDecoratorFunction); - TGocciaClassValue(ClassVal).AddFieldInitializer( - Name, DecoratorResult, IsPrivate, IsStatic); + TGocciaClassValue(ClassVal).AddFieldInitializerWithKey( + ElementName, ElementKey, DecoratorResult, IsPrivate, IsStatic); end; 'a': begin @@ -6180,28 +6439,22 @@ procedure TGocciaVM.ApplyElementDecorator(const ADecoratorFn: TGocciaValue; if Assigned(NewGetter) and not (NewGetter is TGocciaUndefinedLiteralValue) then begin - if IsStatic then - TGocciaClassValue(ClassVal).AddStaticGetter( - Name, TGocciaFunctionBase(NewGetter)) - else - TGocciaClassValue(ClassVal).AddGetter( - Name, TGocciaFunctionBase(NewGetter)); + GetterValue := NewGetter; + DefineDecoratedGetterProperty( + IsStatic, ElementName, ElementKey, GetterValue); end; if Assigned(NewSetter) and not (NewSetter is TGocciaUndefinedLiteralValue) then begin - if IsStatic then - TGocciaClassValue(ClassVal).AddStaticSetter( - Name, TGocciaFunctionBase(NewSetter)) - else - TGocciaClassValue(ClassVal).AddSetter( - Name, TGocciaFunctionBase(NewSetter)); + SetterValue := NewSetter; + DefineDecoratedSetterProperty( + IsStatic, ElementName, ElementKey, SetterValue); end; if Assigned(NewInit) and not (NewInit is TGocciaUndefinedLiteralValue) and NewInit.IsCallable then - TGocciaClassValue(ClassVal).AddFieldInitializer( - Name, NewInit, IsPrivate, IsStatic); + TGocciaClassValue(ClassVal).AddFieldInitializerWithKey( + ElementName, ElementKey, NewInit, IsPrivate, IsStatic); end; end; end; @@ -6226,6 +6479,7 @@ function TGocciaVM.FinishDecorators(const ACurrentValue: TGocciaValue): TGocciaV Exit(ACurrentValue); end; + RunStaticDecoratorInitializersForSession(Session); ClassVal := TGocciaClassValue(Session.ClassValue); ClassVal.DefineSymbolProperty( TGocciaSymbolValue.WellKnownMetadata, @@ -7424,6 +7678,10 @@ function TGocciaVM.ExecuteClosureRegistersInternal( OP_TO_OBJECT: SetRegister(A, ToObject(GetRegister(B))); + // ES2026 §7.1.19 ToPropertyKey(argument) + OP_TO_PROPERTY_KEY: + SetRegister(A, ToPropertyKey(RegisterToValue(FRegisters[B]))); + OP_LOAD_INT: FRegisters[A] := RegisterInt(DecodesBx(Instruction)); @@ -8157,6 +8415,44 @@ // ES2022 §15.7.14: execute static block closure with this = class SetPropertyValue(GetRegister(A), GlobalName, RightValue); end; + OP_DEFINE_PROP_DYNAMIC: + begin + RightValue := RegisterToValue(FRegisters[C]); + TargetValue := GetRegister(A); + if (TargetValue is TGocciaObjectValue) and + (FRegisters[B].Kind = grkObject) and + (FRegisters[B].ObjectValue is TGocciaSymbolValue) then + TGocciaObjectValue(TargetValue).DefineSymbolProperty( + TGocciaSymbolValue(FRegisters[B].ObjectValue), + TGocciaPropertyDescriptorData.Create( + RightValue, [pfEnumerable, pfConfigurable, pfWritable])) + else if (TargetValue is TGocciaObjectValue) and + TryResolveObjectKey(FRegisters[B], PropKeyValue) then + begin + if PropKeyValue is TGocciaSymbolValue then + TGocciaObjectValue(TargetValue).DefineSymbolProperty( + TGocciaSymbolValue(PropKeyValue), + TGocciaPropertyDescriptorData.Create( + RightValue, [pfEnumerable, pfConfigurable, pfWritable])) + else + TGocciaObjectValue(TargetValue).DefineProperty( + TGocciaStringLiteralValue(PropKeyValue).Value, + TGocciaPropertyDescriptorData.Create( + RightValue, [pfEnumerable, pfConfigurable, pfWritable])); + end + else + begin + GlobalName := KeyToPropertyNameRegister(FRegisters[B]); + if TargetValue is TGocciaObjectValue then + TGocciaObjectValue(TargetValue).DefineProperty( + GlobalName, + TGocciaPropertyDescriptorData.Create( + RightValue, [pfEnumerable, pfConfigurable, pfWritable])) + else + SetPropertyValue(TargetValue, GlobalName, RightValue); + end; + end; + OP_DEFINE_STATIC_METHOD_CONST: begin GlobalName := Template.GetConstantUnchecked(B).StringValue; @@ -9658,14 +9954,24 @@ // ES2022 §15.7.14: execute static block closure with this = class end; OP_SETUP_AUTO_ACCESSOR_CONST: - SetupAutoAccessorValue(Template.GetConstantUnchecked(C).StringValue); + SetupAutoAccessorValue(Template.GetConstantUnchecked(C).StringValue, + B <> 0); + + OP_SETUP_AUTO_ACCESSOR_DYNAMIC: + SetupAutoAccessorValueByKey(RegisterToValue(FRegisters[A]), + Template.GetConstantUnchecked(C).StringValue, B <> 0); OP_BEGIN_DECORATORS: BeginDecorators(RegisterToValue(FRegisters[A]), RegisterToValue(FRegisters[A + 1])); OP_APPLY_ELEMENT_DECORATOR_CONST: - ApplyElementDecorator(RegisterToValue(FRegisters[A]), - Template.GetConstantUnchecked(C).StringValue); + if B <> 0 then + ApplyElementDecorator(RegisterToValue(FRegisters[A]), + Template.GetConstantUnchecked(C).StringValue, + RegisterToValue(FRegisters[B])) + else + ApplyElementDecorator(RegisterToValue(FRegisters[A]), + Template.GetConstantUnchecked(C).StringValue); OP_APPLY_CLASS_DECORATOR: ApplyClassDecorator(RegisterToValue(FRegisters[A])); diff --git a/source/units/Goccia.Values.ClassValue.pas b/source/units/Goccia.Values.ClassValue.pas index 752add63..8593dc59 100644 --- a/source/units/Goccia.Values.ClassValue.pas +++ b/source/units/Goccia.Values.ClassValue.pas @@ -26,6 +26,17 @@ TGocciaInstanceValue = class; TGocciaClassFieldOrderEntry = record Name: string; IsPrivate: Boolean; + IsComputed: Boolean; + ComputedKey: TGocciaValue; + Initializer: TGocciaExpression; + end; + + TGocciaDecoratorFieldInitializerEntry = record + Name: string; + ComputedKey: TGocciaValue; + Initializer: TGocciaValue; + IsPrivate: Boolean; + IsStatic: Boolean; end; TGocciaClassValue = class(TGocciaObjectValue) @@ -49,12 +60,8 @@ TGocciaClassValue = class(TGocciaObjectValue) FPrivateBrandToken: string; FMethodInitializers: array of TGocciaValue; FFieldInitializers: array of TGocciaValue; - FDecoratorFieldInitializers: array of record - Name: string; - Initializer: TGocciaValue; - IsPrivate: Boolean; - IsStatic: Boolean; - end; + FDecoratorFieldInitializers: array of TGocciaDecoratorFieldInitializerEntry; + FStaticDecoratorFieldInitializers: array of TGocciaDecoratorFieldInitializerEntry; function GetPropertyGetter(const AName: string): TGocciaFunctionBase; inline; function GetPropertySetter(const AName: string): TGocciaFunctionBase; inline; function GetStaticPropertyGetter(const AName: string): TGocciaFunctionBase; inline; @@ -139,14 +146,17 @@ TGocciaClassValue = class(TGocciaObjectValue) procedure MarkReferences; override; procedure AddFieldInitializer(const AName: string; const AInitializer: TGocciaValue; const AIsPrivate, AIsStatic: Boolean); + procedure AddFieldInitializerWithKey(const AName: string; const AComputedKey: TGocciaValue; const AInitializer: TGocciaValue; const AIsPrivate, AIsStatic: Boolean); procedure SetMethodInitializers(const AInitializers: array of TGocciaValue); procedure SetFieldInitializers(const AInitializers: array of TGocciaValue); procedure AppendMethodInitializers(const AInitializers: array of TGocciaValue); procedure AppendFieldInitializers(const AInitializers: array of TGocciaValue); procedure AddAutoAccessor(const AName, ABackingName: string; const AIsStatic: Boolean); + procedure AddAutoAccessorWithKey(const AName: string; const AKey: TGocciaValue; const ABackingName: string; const AIsStatic: Boolean); procedure RunMethodInitializers(const AInstance: TGocciaValue); procedure RunFieldInitializers(const AInstance: TGocciaValue); procedure RunDecoratorFieldInitializers(const AInstance: TGocciaValue); + procedure RunDecoratorStaticFieldInitializers; end; TGocciaArrayClassValue = class(TGocciaClassValue) @@ -435,8 +445,22 @@ procedure TGocciaClassValue.MarkReferences; if Assigned(FFieldInitializers[I]) then FFieldInitializers[I].MarkReferences; for I := 0 to High(FDecoratorFieldInitializers) do + begin if Assigned(FDecoratorFieldInitializers[I].Initializer) then FDecoratorFieldInitializers[I].Initializer.MarkReferences; + if Assigned(FDecoratorFieldInitializers[I].ComputedKey) then + FDecoratorFieldInitializers[I].ComputedKey.MarkReferences; + end; + for I := 0 to High(FStaticDecoratorFieldInitializers) do + begin + if Assigned(FStaticDecoratorFieldInitializers[I].Initializer) then + FStaticDecoratorFieldInitializers[I].Initializer.MarkReferences; + if Assigned(FStaticDecoratorFieldInitializers[I].ComputedKey) then + FStaticDecoratorFieldInitializers[I].ComputedKey.MarkReferences; + end; + for I := 0 to High(FFieldOrder) do + if Assigned(FFieldOrder[I].ComputedKey) then + FFieldOrder[I].ComputedKey.MarkReferences; end; function TGocciaClassValue.IsCallable: Boolean; @@ -703,11 +727,34 @@ function TGocciaClassValue.GetStaticPropertySetter(const AName: string): TGoccia procedure TGocciaClassValue.AddFieldInitializer(const AName: string; const AInitializer: TGocciaValue; const AIsPrivate, AIsStatic: Boolean); begin - SetLength(FDecoratorFieldInitializers, Length(FDecoratorFieldInitializers) + 1); - FDecoratorFieldInitializers[High(FDecoratorFieldInitializers)].Name := AName; - FDecoratorFieldInitializers[High(FDecoratorFieldInitializers)].Initializer := AInitializer; - FDecoratorFieldInitializers[High(FDecoratorFieldInitializers)].IsPrivate := AIsPrivate; - FDecoratorFieldInitializers[High(FDecoratorFieldInitializers)].IsStatic := AIsStatic; + AddFieldInitializerWithKey(AName, nil, AInitializer, AIsPrivate, AIsStatic); +end; + +procedure TGocciaClassValue.AddFieldInitializerWithKey(const AName: string; const AComputedKey: TGocciaValue; const AInitializer: TGocciaValue; const AIsPrivate, AIsStatic: Boolean); +var + EntryIndex: Integer; +begin + if AIsStatic then + begin + SetLength(FStaticDecoratorFieldInitializers, + Length(FStaticDecoratorFieldInitializers) + 1); + EntryIndex := High(FStaticDecoratorFieldInitializers); + FStaticDecoratorFieldInitializers[EntryIndex].Name := AName; + FStaticDecoratorFieldInitializers[EntryIndex].ComputedKey := AComputedKey; + FStaticDecoratorFieldInitializers[EntryIndex].Initializer := AInitializer; + FStaticDecoratorFieldInitializers[EntryIndex].IsPrivate := AIsPrivate; + FStaticDecoratorFieldInitializers[EntryIndex].IsStatic := AIsStatic; + Exit; + end; + + SetLength(FDecoratorFieldInitializers, + Length(FDecoratorFieldInitializers) + 1); + EntryIndex := High(FDecoratorFieldInitializers); + FDecoratorFieldInitializers[EntryIndex].Name := AName; + FDecoratorFieldInitializers[EntryIndex].ComputedKey := AComputedKey; + FDecoratorFieldInitializers[EntryIndex].Initializer := AInitializer; + FDecoratorFieldInitializers[EntryIndex].IsPrivate := AIsPrivate; + FDecoratorFieldInitializers[EntryIndex].IsStatic := AIsStatic; end; procedure TGocciaClassValue.SetMethodInitializers(const AInitializers: array of TGocciaValue); @@ -750,25 +797,41 @@ procedure TGocciaClassValue.AppendFieldInitializers(const AInitializers: array o // TC39 proposal-decorators: auto-accessor creates backing getter/setter procedure TGocciaClassValue.AddAutoAccessor(const AName, ABackingName: string; const AIsStatic: Boolean); +begin + AddAutoAccessorWithKey(AName, nil, ABackingName, AIsStatic); +end; + +procedure TGocciaClassValue.AddAutoAccessorWithKey(const AName: string; const AKey: TGocciaValue; const ABackingName: string; const AIsStatic: Boolean); var GetterHelper: TGocciaAutoAccessorGetter; SetterHelper: TGocciaAutoAccessorSetter; GetterFn, SetterFn: TGocciaNativeFunctionValue; Target: TGocciaObjectValue; + PropertyName: string; begin GetterHelper := TGocciaAutoAccessorGetter.Create(ABackingName); SetterHelper := TGocciaAutoAccessorSetter.Create(ABackingName); - GetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(GetterHelper.Get, 'get ' + AName, 0); - SetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(SetterHelper.SetValue, 'set ' + AName, 1); + if Assigned(AKey) and not (AKey is TGocciaSymbolValue) then + PropertyName := AKey.ToStringLiteral.Value + else + PropertyName := AName; + + GetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(GetterHelper.Get, 'get ' + PropertyName, 0); + SetterFn := TGocciaNativeFunctionValue.CreateWithoutPrototype(SetterHelper.SetValue, 'set ' + PropertyName, 1); // Static auto-accessors go on the constructor; instance ones on the prototype if AIsStatic then Target := Self else Target := FClassPrototype; - Target.DefineProperty(AName, TGocciaPropertyDescriptorAccessor.Create( - GetterFn, SetterFn, [pfConfigurable, pfWritable])); + if AKey is TGocciaSymbolValue then + Target.DefineSymbolProperty(TGocciaSymbolValue(AKey), + TGocciaPropertyDescriptorAccessor.Create( + GetterFn, SetterFn, [pfConfigurable, pfWritable])) + else + Target.DefineProperty(PropertyName, TGocciaPropertyDescriptorAccessor.Create( + GetterFn, SetterFn, [pfConfigurable, pfWritable])); end; procedure TGocciaClassValue.RunMethodInitializers(const AInstance: TGocciaValue); @@ -808,6 +871,8 @@ procedure TGocciaClassValue.RunDecoratorFieldInitializers(const AInstance: TGocc Idx: Integer; Args: TGocciaArgumentsCollection; InitResult, OriginalValue: TGocciaValue; + PropertyKey: TGocciaValue; + PropertyName: string; begin for Idx := 0 to High(FDecoratorFieldInitializers) do begin @@ -816,7 +881,18 @@ procedure TGocciaClassValue.RunDecoratorFieldInitializers(const AInstance: TGocc if Assigned(AInstance) and (AInstance is TGocciaObjectValue) then begin - OriginalValue := TGocciaObjectValue(AInstance).GetProperty(FDecoratorFieldInitializers[Idx].Name); + PropertyKey := FDecoratorFieldInitializers[Idx].ComputedKey; + if PropertyKey is TGocciaSymbolValue then + OriginalValue := TGocciaObjectValue(AInstance).GetSymbolProperty( + TGocciaSymbolValue(PropertyKey)) + else + begin + if Assigned(PropertyKey) then + PropertyName := PropertyKey.ToStringLiteral.Value + else + PropertyName := FDecoratorFieldInitializers[Idx].Name; + OriginalValue := TGocciaObjectValue(AInstance).GetProperty(PropertyName); + end; if not Assigned(OriginalValue) then OriginalValue := TGocciaUndefinedLiteralValue.UndefinedValue; @@ -824,7 +900,13 @@ procedure TGocciaClassValue.RunDecoratorFieldInitializers(const AInstance: TGocc try InitResult := TGocciaFunctionBase(FDecoratorFieldInitializers[Idx].Initializer).Call(Args, AInstance); if Assigned(InitResult) and not (InitResult is TGocciaUndefinedLiteralValue) then - TGocciaObjectValue(AInstance).AssignProperty(FDecoratorFieldInitializers[Idx].Name, InitResult); + begin + if PropertyKey is TGocciaSymbolValue then + TGocciaObjectValue(AInstance).AssignSymbolProperty( + TGocciaSymbolValue(PropertyKey), InitResult) + else + TGocciaObjectValue(AInstance).AssignProperty(PropertyName, InitResult); + end; finally Args.Free; end; @@ -832,6 +914,56 @@ procedure TGocciaClassValue.RunDecoratorFieldInitializers(const AInstance: TGocc end; end; +procedure TGocciaClassValue.RunDecoratorStaticFieldInitializers; +var + Idx: Integer; + Args: TGocciaArgumentsCollection; + InitResult, OriginalValue: TGocciaValue; + PropertyKey: TGocciaValue; + PropertyName: string; +begin + for Idx := 0 to High(FStaticDecoratorFieldInitializers) do + begin + PropertyKey := FStaticDecoratorFieldInitializers[Idx].ComputedKey; + if FStaticDecoratorFieldInitializers[Idx].IsPrivate then + begin + if not FPrivateStaticProperties.TryGetValue( + FStaticDecoratorFieldInitializers[Idx].Name, OriginalValue) then + OriginalValue := TGocciaUndefinedLiteralValue.UndefinedValue; + end + else if PropertyKey is TGocciaSymbolValue then + OriginalValue := GetSymbolProperty(TGocciaSymbolValue(PropertyKey)) + else + begin + if Assigned(PropertyKey) then + PropertyName := PropertyKey.ToStringLiteral.Value + else + PropertyName := FStaticDecoratorFieldInitializers[Idx].Name; + OriginalValue := GetProperty(PropertyName); + end; + if not Assigned(OriginalValue) then + OriginalValue := TGocciaUndefinedLiteralValue.UndefinedValue; + + Args := TGocciaArgumentsCollection.Create([OriginalValue]); + try + InitResult := TGocciaFunctionBase( + FStaticDecoratorFieldInitializers[Idx].Initializer).Call(Args, Self); + if Assigned(InitResult) and not (InitResult is TGocciaUndefinedLiteralValue) then + begin + if FStaticDecoratorFieldInitializers[Idx].IsPrivate then + AddPrivateStaticProperty( + FStaticDecoratorFieldInitializers[Idx].Name, InitResult) + else if PropertyKey is TGocciaSymbolValue then + AssignSymbolProperty(TGocciaSymbolValue(PropertyKey), InitResult) + else + SetProperty(PropertyName, InitResult); + end; + finally + Args.Free; + end; + end; +end; + procedure TGocciaClassValue.AddInstanceProperty(const AName: string; const AExpression: TGocciaExpression); begin FInstancePropertyDefs.Add(AName, AExpression); diff --git a/tests/language/classes/class-computed-field-yield-order.js b/tests/language/classes/class-computed-field-yield-order.js new file mode 100644 index 00000000..ddbfa4bc --- /dev/null +++ b/tests/language/classes/class-computed-field-yield-order.js @@ -0,0 +1,69 @@ +/*--- +description: Computed public class field names evaluate in source order +features: [computed-property-names, class-fields-public, class-static-fields-public, generators] +---*/ + +test("computed public field names follow source order", () => { + const obj = { + *makeClass() { + let C = class { + static [yield "static-before"] = 7; + [yield "field"] = 42; + }; + let c = new C(); + return [C.staticBefore, c.field]; + }, + }; + + const iter = obj.makeClass(); + + expect(iter.next()).toEqual({ value: "static-before", done: false }); + expect(iter.next("staticBefore")).toEqual({ value: "field", done: false }); + expect(iter.next("field")).toEqual({ + value: [7, 42], + done: true, + }); +}); + +test("mixed computed public field names resume in source order", () => { + const obj = { + *makeClass() { + let C = class { + static [yield "static-before"] = 7; + [yield "field"] = 42; + static [yield "static-after"] = 9; + }; + let c = new C(); + return [C.staticBefore, c.field, C.staticAfter]; + }, + }; + + const iter = obj.makeClass(); + + expect(iter.next()).toEqual({ value: "static-before", done: false }); + expect(iter.next("staticBefore")).toEqual({ value: "field", done: false }); + expect(iter.next("field")).toEqual({ value: "static-after", done: false }); + expect(iter.next("staticAfter")).toEqual({ + value: [7, 42, 9], + done: true, + }); +}); + +test("computed public instance fields define own properties", () => { + let calls = 0; + + class Base { + set value(next) { + calls = calls + next; + } + } + + class Derived extends Base { + ["value"] = 11; + } + + const instance = new Derived(); + + expect(calls).toBe(0); + expect(instance.value).toBe(11); +}); diff --git a/tests/language/classes/computed-field-key-coercion.js b/tests/language/classes/computed-field-key-coercion.js new file mode 100644 index 00000000..324e295e --- /dev/null +++ b/tests/language/classes/computed-field-key-coercion.js @@ -0,0 +1,44 @@ +/*--- +description: Computed public class field keys are coerced during class definition +features: [computed-property-names, class-fields-public, class-static-fields-public] +---*/ + +test("static computed field coerces key before initializer", () => { + const order = []; + const key = { + toString() { + order.push("key"); + return "value"; + }, + }; + + class C { + static [key] = (order.push("init"), 1); + } + + expect(order).toEqual(["key", "init"]); + expect(C.value).toBe(1); +}); + +test("instance computed field coerces key once at class definition", () => { + const order = []; + const key = { + toString() { + order.push("key"); + return "value"; + }, + }; + + class C { + [key] = (order.push("init"), 1); + } + + expect(order).toEqual(["key"]); + + const first = new C(); + const second = new C(); + + expect(order).toEqual(["key", "init", "init"]); + expect(first.value).toBe(1); + expect(second.value).toBe(1); +}); diff --git a/tests/language/decorators/auto-accessor-decorator.js b/tests/language/decorators/auto-accessor-decorator.js index 8fcd085c..bb14f8cd 100644 --- a/tests/language/decorators/auto-accessor-decorator.js +++ b/tests/language/decorators/auto-accessor-decorator.js @@ -22,4 +22,55 @@ describe("auto-accessor decorators", () => { expect(receivedContext.name).toBe("x"); expect(typeof receivedValue).toBe("object"); }); + + test("computed accessor decorator context and access use resolved symbol key", () => { + let receivedName; + let accessGet; + const key = Symbol("accessor"); + const decorate = (value, context) => { + receivedName = context.name; + accessGet = context.access.get; + return { + get() { + return value.get.call(this) + 1; + } + }; + }; + + class C { + @decorate + accessor [key] = 41; + } + + const instance = new C(); + expect(receivedName === key).toBe(true); + expect(instance[key]).toBe(42); + expect(accessGet(instance)).toBe(42); + }); + + test("static accessor decorator initializer runs before class replacement", () => { + let observedOriginalValue; + const accessor = (value, context) => { + return { + init() { + return 42; + } + }; + }; + const replace = (cls, context) => { + observedOriginalValue = cls.value; + return class Replacement { + static value = 100; + }; + }; + + @replace + class C { + @accessor + static accessor value = 41; + } + + expect(observedOriginalValue).toBe(42); + expect(C.value).toBe(100); + }); }); diff --git a/tests/language/decorators/auto-accessor.js b/tests/language/decorators/auto-accessor.js index d490d01a..8397d38d 100644 --- a/tests/language/decorators/auto-accessor.js +++ b/tests/language/decorators/auto-accessor.js @@ -31,4 +31,30 @@ describe("auto-accessor", () => { const c = new C(); expect(c.x).toBe(undefined); }); + + test("computed auto-accessor uses resolved property key", () => { + const key = "x"; + + class C { + accessor [key] = 1; + } + + const c = new C(); + expect(c.x).toBe(1); + c.x = 2; + expect(c.x).toBe(2); + }); + + test("symbol computed auto-accessor uses resolved property key", () => { + const key = Symbol("x"); + + class C { + accessor [key] = 1; + } + + const c = new C(); + expect(c[key]).toBe(1); + c[key] = 2; + expect(c[key]).toBe(2); + }); }); diff --git a/tests/language/decorators/basic-class-decorator.js b/tests/language/decorators/basic-class-decorator.js index 2ae06701..c0470278 100644 --- a/tests/language/decorators/basic-class-decorator.js +++ b/tests/language/decorators/basic-class-decorator.js @@ -78,4 +78,26 @@ describe("class decorators", () => { expect(c.x).toBe(1); expect(c.wrapped).toBe(true); }); + + test("static field decorator initializer runs before class replacement", () => { + let observedOriginalValue; + const field = (value, context) => { + return (initialValue) => initialValue + 1; + }; + const replace = (cls, context) => { + observedOriginalValue = cls.value; + return class Replacement { + static value = 100; + }; + }; + + @replace + class C { + @field + static value = 41; + } + + expect(observedOriginalValue).toBe(42); + expect(C.value).toBe(100); + }); }); diff --git a/tests/language/decorators/computed-field-decorator.js b/tests/language/decorators/computed-field-decorator.js new file mode 100644 index 00000000..354ff58f --- /dev/null +++ b/tests/language/decorators/computed-field-decorator.js @@ -0,0 +1,293 @@ +/*--- +description: Decorated computed fields use the resolved property key in context and initializer wiring +features: [decorators, class-fields, computed-property-names] +---*/ + +describe("computed field decorators", () => { + test("string computed field context and initializer use resolved key", () => { + let receivedName; + const key = "x"; + const decorate = (value, context) => { + receivedName = context.name; + return (initialValue) => initialValue + 1; + }; + + class C { + @decorate + [key] = 41; + } + + const instance = new C(); + expect(receivedName).toBe("x"); + expect(instance.x).toBe(42); + }); + + test("symbol computed field context and initializer preserve symbol key", () => { + let receivedName; + const key = Symbol("field"); + const decorate = (value, context) => { + receivedName = context.name; + return (initialValue) => initialValue + 1; + }; + + class C { + @decorate + [key] = 41; + } + + const instance = new C(); + expect(receivedName === key).toBe(true); + expect(instance[key]).toBe(42); + }); + + test("symbol computed field access helper preserves symbol key", () => { + const key = Symbol("field-access"); + const decorate = (value, context) => { + context.addInitializer(({ init() { context.access.set(this, 42); } }).init); + }; + + class C { + @decorate + [key] = 1; + } + + expect(new C()[key]).toBe(42); + }); + + test("static symbol computed field access helper preserves class symbol key", () => { + const key = Symbol("static-field-access"); + const decorate = (value, context) => { + context.addInitializer(({ init() { context.access.set(this, 42); } }).init); + }; + + class C { + @decorate + static [key] = 1; + } + + expect(C[key]).toBe(42); + }); + + test("symbol computed method decorator uses resolved key", () => { + let receivedName; + let accessGet; + const key = Symbol("method"); + const decorate = (value, context) => { + receivedName = context.name; + accessGet = context.access.get; + return () => 42; + }; + + class C { + @decorate + [key]() { + return 1; + } + } + + expect(receivedName === key).toBe(true); + const instance = new C(); + expect(instance[key]()).toBe(42); + expect(accessGet(instance)()).toBe(42); + }); + + test("decorated computed method context uses property-key coercion", () => { + let receivedName; + let toStringCalls = 0; + const key = { + toString() { + toStringCalls++; + return "coerced"; + } + }; + const decorate = (value, context) => { + receivedName = context.name; + return value; + }; + + class C { + @decorate + [key]() { + return 42; + } + } + + expect(receivedName).toBe("coerced"); + expect(toStringCalls).toBe(1); + expect(new C().coerced()).toBe(42); + }); + + test("static symbol computed method access helper preserves class symbol key", () => { + let accessGet; + const key = Symbol("static-method-access"); + const decorate = (value, context) => { + accessGet = context.access.get; + return () => 42; + }; + + class C { + @decorate + static [key]() { + return 1; + } + } + + expect(C[key]()).toBe(42); + expect(accessGet(C)()).toBe(42); + }); + + test("symbol computed getter decorator uses resolved key", () => { + let receivedName; + let accessGet; + const key = Symbol("getter"); + const decorate = (value, context) => { + receivedName = context.name; + accessGet = context.access.get; + return () => 42; + }; + + class C { + @decorate + get [key]() { + return 1; + } + } + + expect(receivedName === key).toBe(true); + const instance = new C(); + expect(instance[key]).toBe(42); + expect(accessGet(instance)).toBe(42); + }); + + test("symbol computed setter decorator uses resolved key", () => { + let receivedName; + let accessSet; + let stored = 0; + const key = Symbol("setter"); + const decorate = (value, context) => { + receivedName = context.name; + accessSet = context.access.set; + return (next) => { + stored = next * 2; + }; + }; + + class C { + @decorate + set [key](next) { + stored = next; + } + } + + const instance = new C(); + instance[key] = 21; + expect(receivedName === key).toBe(true); + expect(stored).toBe(42); + accessSet(instance, 7); + expect(stored).toBe(14); + }); + + test("static computed field context uses resolved key", () => { + let receivedName; + const key = "staticName"; + const decorate = (value, context) => { + receivedName = context.name; + }; + + class C { + @decorate + static [key] = 1; + } + + expect(receivedName).toBe("staticName"); + expect(C.staticName).toBe(1); + }); + + test("static string computed field decorator initializer updates resolved key", () => { + const key = "staticInit"; + const decorate = (value, context) => { + return (initialValue) => initialValue + 1; + }; + + class C { + @decorate + static [key] = 41; + } + + expect(C.staticInit).toBe(42); + }); + + test("static symbol computed field decorator initializer updates resolved key", () => { + const key = Symbol("static-init"); + const decorate = (value, context) => { + return (initialValue) => initialValue + 1; + }; + + class C { + @decorate + static [key] = 41; + } + + expect(C[key]).toBe(42); + }); + + test("static string computed field access helper writes through class properties", () => { + const key = "name"; + const decorate = (value, context) => { + context.addInitializer(({ init() { + context.access.set(this, "DecoratedName"); + } }).init); + }; + + class Original { + @decorate + static [key] = "InitialName"; + } + + expect(Original.name).toBe("DecoratedName"); + }); + + test("decorated computed methods remain non-enumerable", () => { + const key = "method"; + const decorate = (value, context) => value; + + class C { + @decorate + [key]() { + return 1; + } + } + + const descriptor = Object.getOwnPropertyDescriptor(C.prototype, key); + expect(descriptor.enumerable).toBe(false); + }); + + test("decorated computed getters remain non-enumerable", () => { + const key = "value"; + const decorate = (value, context) => value; + + class C { + @decorate + get [key]() { + return 1; + } + } + + const descriptor = Object.getOwnPropertyDescriptor(C.prototype, key); + expect(descriptor.enumerable).toBe(false); + }); + + test("decorated computed setters remain non-enumerable", () => { + const key = "value"; + const decorate = (value, context) => value; + + class C { + @decorate + set [key](next) { + } + } + + const descriptor = Object.getOwnPropertyDescriptor(C.prototype, key); + expect(descriptor.enumerable).toBe(false); + }); +}); diff --git a/tests/language/function-keyword/async-function.js b/tests/language/function-keyword/async-function.js index 68d2fc27..52472725 100644 --- a/tests/language/function-keyword/async-function.js +++ b/tests/language/function-keyword/async-function.js @@ -19,6 +19,15 @@ test("async function expression", async () => { expect(result).toBe("hello"); }); +test("named async function expression source text starts at async keyword", () => { + const source = (async function getValue(value) { + return value; + }).toString(); + + expect(source.startsWith("async function getValue(value)")).toBe(true); + expect(source.includes("return value")).toBe(true); +}); + test("async function with await", async () => { async function delayed() { const value = await Promise.resolve(10); diff --git a/tests/language/function-keyword/class-computed-field-yield.js b/tests/language/function-keyword/class-computed-field-yield.js new file mode 100644 index 00000000..8eaecd06 --- /dev/null +++ b/tests/language/function-keyword/class-computed-field-yield.js @@ -0,0 +1,73 @@ +/*--- +description: Generator yield resumes computed public class field names +features: [compat-function, computed-property-names, class-fields-public, class-static-fields-public, generators] +---*/ + +test("yield resumes computed public instance and static field names", () => { + function* makeClass() { + let C = class { + [yield "instance-key"] = 9; + static [yield "static-key"] = 10; + }; + let c = new C(); + return [c, C]; + } + + const instanceKey = Symbol("instance"); + const staticKey = Symbol("static"); + const iter = makeClass(); + + expect(iter.next()).toEqual({ value: "instance-key", done: false }); + expect(iter.next(instanceKey)).toEqual({ value: "static-key", done: false }); + + const result = iter.next(staticKey); + expect(result.done).toBe(true); + expect(result.value[0][instanceKey]).toBe(9); + expect(result.value[1][staticKey]).toBe(10); +}); + +test("omitted next values install fields under undefined keys", () => { + let observed = []; + + function* makeClass() { + let C = class { + [yield 9] = 9; + static [yield 9] = 10; + }; + let c = new C(); + observed = [c[undefined], C[undefined], c[9], C[9]]; + } + + const iter = makeClass(); + + expect(iter.next()).toEqual({ value: 9, done: false }); + expect(iter.next()).toEqual({ value: 9, done: false }); + expect(iter.next()).toEqual({ value: undefined, done: true }); + expect(observed).toEqual([9, 10, undefined, undefined]); +}); + +test("computed field initializers from yield remain callable", () => { + function* makeClass() { + let C = class { + [yield 9] = () => 9; + static [yield 9] = () => 10; + }; + let c = new C(); + return [ + c[yield 9](), + C[yield 9](), + c[String(yield 9)](), + C[String(yield 9)](), + ]; + } + + const iter = makeClass(); + + expect(iter.next()).toEqual({ value: 9, done: false }); + expect(iter.next(9)).toEqual({ value: 9, done: false }); + expect(iter.next(9)).toEqual({ value: 9, done: false }); + expect(iter.next(9)).toEqual({ value: 9, done: false }); + expect(iter.next(9)).toEqual({ value: 9, done: false }); + expect(iter.next(9)).toEqual({ value: 9, done: false }); + expect(iter.next(9)).toEqual({ value: [9, 10, 9, 10], done: true }); +});