From 59abe71d1d51ac7726095bd124c2c5b008f390d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= Date: Sun, 23 Feb 2025 23:40:49 +0100 Subject: [PATCH] binding callable convert, not implemented yet --- .../CodeGen/Graph/BoundExpression.cs | 15 ++-- .../FlowAnalysis/ExpressionAnalysis.cs | 7 ++ .../Passes/TransformationRewriter.cs | 6 +- .../Semantics/BoundExpression.cs | 87 ++++++++++++++++++- .../Semantics/PhpOperationVisitor.cs | 2 + .../Semantics/SemanticsBinder.cs | 32 ++++--- 6 files changed, 127 insertions(+), 22 deletions(-) diff --git a/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs b/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs index b6d78f8c21..6e56345f54 100644 --- a/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs +++ b/src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs @@ -2351,6 +2351,14 @@ internal override TypeSymbol Emit(CodeGenerator cg) } partial class BoundCallableConvert + { + internal override TypeSymbol Emit(CodeGenerator cg) + { + throw new NotImplementedException(); + } + } + + partial class BoundConvertToCallable { internal override TypeSymbol Emit(CodeGenerator cg) { @@ -3384,7 +3392,7 @@ internal override TypeSymbol Emit(CodeGenerator cg) cg.EmitLoadContext(); // Context idxfld.EmitLoad(cg); // routine - EmitThis(cg); // $this + cg.EmitPhpThisOrNull(); // $this cg.EmitCallerTypeHandle(); // scope EmitStaticType(cg); // statictype : PhpTypeInfo EmitCachedParametersArray(cg, ((LambdaFunctionExpr)PhpSyntax).Signature.FormalParamsFixed()); // "parameters" @@ -3393,11 +3401,6 @@ internal override TypeSymbol Emit(CodeGenerator cg) return cg.EmitCall(ILOpCode.Call, cg.CoreMethods.Operators.BuildClosure_Context_IPhpCallable_Object_RuntimeTypeHandle_PhpTypeInfo_PhpArray_PhpArray); } - void EmitThis(CodeGenerator cg) - { - cg.EmitPhpThisOrNull(); - } - void EmitStaticType(CodeGenerator cg) { if ((cg.Routine.Flags & FlowAnalysis.RoutineFlags.UsesLateStatic) != 0) diff --git a/src/Peachpie.CodeAnalysis/FlowAnalysis/ExpressionAnalysis.cs b/src/Peachpie.CodeAnalysis/FlowAnalysis/ExpressionAnalysis.cs index 9299831d52..aa2b0f0cf4 100644 --- a/src/Peachpie.CodeAnalysis/FlowAnalysis/ExpressionAnalysis.cs +++ b/src/Peachpie.CodeAnalysis/FlowAnalysis/ExpressionAnalysis.cs @@ -1574,6 +1574,13 @@ public override T VisitConversion(BoundConversionEx x) return default; } + public override T VisitCallableConvert(BoundCallableConvert x) + { + x.TypeRefMask = TypeCtx.GetClosureTypeMask(); + + return base.VisitCallableConvert(x); + } + #endregion #region Visit InstanceOf diff --git a/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/TransformationRewriter.cs b/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/TransformationRewriter.cs index f053cbdb73..ef348de038 100644 --- a/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/TransformationRewriter.cs +++ b/src/Peachpie.CodeAnalysis/FlowAnalysis/Passes/TransformationRewriter.cs @@ -735,7 +735,7 @@ public override object VisitLiteral(BoundLiteral x) { TransformationCount++; x.Access = x.Access.WithRead(DeclaringCompilation.CoreTypes.String); // read the literal as string, do not rewrite it to BoundCallableConvert again - return new BoundCallableConvert(x, DeclaringCompilation) { TargetCallable = symbol }.WithContext(x); + return new BoundConvertToCallable(x, DeclaringCompilation) { TargetCallable = symbol }.WithContext(x); } } @@ -813,13 +813,13 @@ bool TryGetMethod(TypeSymbol typeSymbol, string methodName, out MethodSymbol met } } - BoundCallableConvert Transform(BoundArrayEx origArray, IMethodSymbol targetCallable, BoundExpression receiver = null) + BoundConvertToCallable Transform(BoundArrayEx origArray, IMethodSymbol targetCallable, BoundExpression receiver = null) { // read the literal as array, do not rewrite it to BoundCallableConvert again origArray.Access = origArray.Access.WithRead(DeclaringCompilation.CoreTypes.PhpArray); TransformationCount++; - return new BoundCallableConvert(origArray, DeclaringCompilation) + return new BoundConvertToCallable(origArray, DeclaringCompilation) { TargetCallable = targetCallable, Receiver = receiver diff --git a/src/Peachpie.CodeAnalysis/Semantics/BoundExpression.cs b/src/Peachpie.CodeAnalysis/Semantics/BoundExpression.cs index 0192f0b46d..3c1a50f0cb 100644 --- a/src/Peachpie.CodeAnalysis/Semantics/BoundExpression.cs +++ b/src/Peachpie.CodeAnalysis/Semantics/BoundExpression.cs @@ -1359,7 +1359,7 @@ internal BoundConversionEx(BoundExpression operand, BoundTypeRef targetType) this.TargetType = targetType ?? throw ExceptionUtilities.ArgumentNull(nameof(targetType)); } - internal BoundConversionEx Update(BoundExpression operand, BoundTypeRef targetType) + internal virtual BoundConversionEx Update(BoundExpression operand, BoundTypeRef targetType) { if (operand == this.Operand && targetType == this.TargetType) { @@ -1379,7 +1379,7 @@ internal BoundConversionEx Update(BoundExpression operand, BoundTypeRef targetTy } /// - /// Conversion to IPhpCallable (callable). + /// Callable convert syntax. /// public partial class BoundCallableConvert : BoundConversionEx { @@ -1391,10 +1391,91 @@ public partial class BoundCallableConvert : BoundConversionEx /// In case of an instance method, this is its receiver instance. internal BoundExpression Receiver { get; set; } - internal BoundCallableConvert(BoundExpression operand, PhpCompilation compilation) + /// In case of a static method, this is its receiver type. + internal IBoundTypeRef StaticReceiver { get; set; } + + /// + /// Bound function name in case it's callable convert. + /// + internal BoundRoutineName Name { get; set; } + + internal BoundCallableConvert(BoundExpression receiver, BoundRoutineName name, PhpCompilation compilation) + : this(new BoundLiteral(null), compilation) + { + this.Receiver = receiver; + this.Name = name; + } + + internal BoundCallableConvert(IBoundTypeRef receiver, BoundRoutineName name, PhpCompilation compilation) + : this(new BoundLiteral(null), compilation) + { + this.StaticReceiver = receiver; + this.Name = name; + } + + private BoundCallableConvert(BoundExpression operand, PhpCompilation compilation) : base(operand, compilation.TypeRefFactory.Create(compilation.CoreTypes.IPhpCallable.Symbol)) { } + + internal override BoundConversionEx Update(BoundExpression operand, BoundTypeRef targetType) + { + throw ExceptionUtilities.Unreachable; + + //if (operand == this.Operand && targetType == this.TargetType) + //{ + // return this; + //} + //else + //{ + // return new BoundCallableConvert(operand, targetType).WithContext(this); + //} + } + + public override TResult Accept(OperationVisitor visitor, TArgument argument) => visitor.VisitConversion(this, argument); + + public override TResult Accept(PhpOperationVisitor visitor) => visitor.VisitCallableConvert(this); + } + + /// + /// Conversion to IPhpCallable (callable). + /// + public partial class BoundConvertToCallable : BoundConversionEx + { + /// + /// Resolved method to be converted to callable. + /// + public IMethodSymbol TargetCallable { get; internal set; } + + /// In case of an instance method, this is its receiver instance. + internal BoundExpression Receiver { get; set; } + + internal BoundConvertToCallable(BoundExpression operand, PhpCompilation compilation) + : this(operand, compilation.TypeRefFactory.Create(compilation.CoreTypes.IPhpCallable.Symbol)) + { + } + + private BoundConvertToCallable(BoundExpression operand, BoundTypeRef targetType) + : base(operand, targetType) + { + + } + + internal override BoundConversionEx Update(BoundExpression operand, BoundTypeRef targetType) + { + if (operand == this.Operand && targetType == this.TargetType) + { + return this; + } + else + { + return new BoundConvertToCallable(operand, targetType) + { + TargetCallable = TargetCallable, + Receiver = Receiver, + }.WithContext(this); + } + } } #endregion diff --git a/src/Peachpie.CodeAnalysis/Semantics/PhpOperationVisitor.cs b/src/Peachpie.CodeAnalysis/Semantics/PhpOperationVisitor.cs index d6cc16bf6e..e391d104f9 100644 --- a/src/Peachpie.CodeAnalysis/Semantics/PhpOperationVisitor.cs +++ b/src/Peachpie.CodeAnalysis/Semantics/PhpOperationVisitor.cs @@ -62,6 +62,8 @@ public abstract class PhpOperationVisitor public virtual TResult VisitConversion(BoundConversionEx x) => DefaultVisitOperation(x); + public virtual TResult VisitCallableConvert(BoundCallableConvert x) => DefaultVisitOperation(x); + public virtual TResult VisitIncDec(BoundIncDecEx x) => DefaultVisitOperation(x); public virtual TResult VisitConditional(BoundConditionalEx x) => DefaultVisitOperation(x); diff --git a/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs b/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs index 14e8f83077..d00f3327f4 100644 --- a/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs +++ b/src/Peachpie.CodeAnalysis/Semantics/SemanticsBinder.cs @@ -820,12 +820,6 @@ protected BoundExpression BindFunctionCall(AST.FunctionCall x, BoundExpression b boundTarget = BindIsMemberOfChain(x.IsMemberOf, BoundAccess.Read/*Object?*/); } - // callable convert syntax (...) - if (x.CallSignature.IsCallableConvert) - { - throw new NotImplementedException("callable convert"); - } - BoundRoutineCall result; if (x is AST.DirectFcnCall) @@ -835,6 +829,11 @@ protected BoundExpression BindFunctionCall(AST.FunctionCall x, BoundExpression b var fname = ((AST.DirectFcnCall)x).FullName; + if (x.CallSignature.IsCallableConvert) // callable convert syntax (...) + { + return new BoundCallableConvert(boundTarget, new BoundRoutineName(fname.Name), DeclaringCompilation); + } + if (boundTarget == null) { if (fname.IsGetArgsOrArgsNumFunctionName() && Routine != null) @@ -863,15 +862,22 @@ protected BoundExpression BindFunctionCall(AST.FunctionCall x, BoundExpression b { Debug.Assert(fname.FallbackName.HasValue == false); Debug.Assert(fname.Name.QualifiedName.IsSimpleName); + result = new BoundInstanceFunctionCall(boundTarget, fname.Name, BindArguments(x.CallSignature.Parameters)); } } - else if (x is AST.IndirectFcnCall) + else if (x is AST.IndirectFcnCall indirect) { // $func(...) // $x->$func(...) - var nameExpr = BindExpression(((AST.IndirectFcnCall)x).NameExpr); + var nameExpr = BindExpression(indirect.NameExpr); + + if (x.CallSignature.IsCallableConvert) // callable convert syntax (...) + { + return new BoundCallableConvert(boundTarget, new BoundRoutineName(nameExpr), DeclaringCompilation); + } + if (boundTarget == null) { result = new BoundGlobalFunctionCall(nameExpr, BindArguments(x.CallSignature.Parameters)); @@ -887,11 +893,17 @@ protected BoundExpression BindFunctionCall(AST.FunctionCall x, BoundExpression b Debug.Assert(boundTarget == null); - var boundname = (staticMtdCall is AST.DirectStMtdCall dm) + var boundStaticTarget = BindTypeRef(staticMtdCall.TargetType, objectTypeInfoSemantic: true); + var boundName = (staticMtdCall is AST.DirectStMtdCall dm) ? new BoundRoutineName(new QualifiedName(dm.MethodName)) : new BoundRoutineName(new BoundConversionEx(BindExpression(((AST.IndirectStMtdCall)staticMtdCall).MethodNameExpression), BoundTypeRefFactory.StringTypeRef)); - result = new BoundStaticFunctionCall(BindTypeRef(staticMtdCall.TargetType, objectTypeInfoSemantic: true), boundname, BindArguments(x.CallSignature.Parameters)); + if (x.CallSignature.IsCallableConvert) // callable convert syntax (...) + { + return new BoundCallableConvert(boundStaticTarget, boundName, DeclaringCompilation); + } + + result = new BoundStaticFunctionCall(boundStaticTarget, boundName, BindArguments(x.CallSignature.Parameters)); } else {