diff --git a/src/Php/Optimizer/FuncCallOptimizer.php b/src/Php/Optimizer/FuncCallOptimizer.php index 4bf4ec19..ef0d95c3 100644 --- a/src/Php/Optimizer/FuncCallOptimizer.php +++ b/src/Php/Optimizer/FuncCallOptimizer.php @@ -344,15 +344,25 @@ trait FuncCallOptimizer { $base = ($type[0] ?? '') === self::ARG_OPTIONAL ? substr($type, 1) : $type; + if ($base === self::ARG_TYPE_ARRAY) { + return $this->convertStdContainerArrayExpr($expr, $index, $this->getArg($expr, $index)); + } + $raw = ($base === self::ARG_TYPE_REF) ? $this->getRefArg($expr, $index) : $this->getArg($expr, $index); + return $this->applyArgConversion($raw, $type); + } + + protected function applyArgConversion(string $cxxExpr, string $type): string + { + $base = ($type[0] ?? '') === self::ARG_OPTIONAL ? substr($type, 1) : $type; return match ($base) { - self::ARG_TYPE_STR => $this->convertStringExpr($raw), - self::ARG_TYPE_INT => $this->convertIntExpr($raw), - self::ARG_TYPE_FLOAT => $this->convertFloatExpr($raw), - self::ARG_TYPE_BOOL => $this->convertBoolExpr($raw), - self::ARG_TYPE_ARRAY => $this->convertStdContainerArrayExpr($expr, $index, $raw), - default => $raw, + self::ARG_TYPE_STR => $this->convertStringExpr($cxxExpr), + self::ARG_TYPE_INT => $this->convertIntExpr($cxxExpr), + self::ARG_TYPE_FLOAT => $this->convertFloatExpr($cxxExpr), + self::ARG_TYPE_BOOL => $this->convertBoolExpr($cxxExpr), + self::ARG_TYPE_ARRAY => $this->convertArrayExpr($cxxExpr), + default => $cxxExpr, }; } diff --git a/src/Php/UniversalMethodCall.php b/src/Php/UniversalMethodCall.php index 6e20f390..2316f822 100644 --- a/src/Php/UniversalMethodCall.php +++ b/src/Php/UniversalMethodCall.php @@ -694,6 +694,83 @@ trait UniversalMethodCall return 'php::' . $fn . '(' . $receiver . ')'; } + private function mergeConstArgs(array $argExprs, array $constArgs): array + { + if (!$constArgs) { + return $argExprs; + } + $maxPos = max(array_keys($constArgs)); + $totalSlots = max(count($argExprs) + count($constArgs), $maxPos + 1); + $merged = []; + $regIdx = 0; + for ($i = 0; $i < $totalSlots; $i++) { + if (isset($constArgs[$i])) { + $merged[] = $constArgs[$i]; + } else { + $merged[] = $argExprs[$regIdx++]; + } + } + return $merged; + } + + /** + * Try to generate a direct C++ call for a php_fn method using the FuncCallOptimizer's + * type-aware argument resolution. Returns null if the function is not optimizable. + */ + private function tryOptimizePhpFn(string $receiver, string $phpFunc, array $args, int $receiverPos, array $constArgs): ?string + { + $config = $this->getFuncCallConfig()[$phpFunc] ?? null; + if ($config === null) { + return null; + } + + $targetName = $phpFunc; + if (is_string($config)) { + $targetName = $config; + $config = $this->getFuncCallConfig()[$targetName] ?? []; + } + + // Skip entries that need FuncCall AST nodes + if (isset($config['handler']) || isset($config['bigDispatch']) || isset($config['conversion'])) { + return null; + } + + $refInfo = $this->getArgReflectionInfo($targetName); + if (!empty($config['variadic']) || ($refInfo['variadic'] ?? false)) { + return null; + } + + $target = $config['target'] ?? null; + if ($target === null) { + $target = 'php::fn::' . $targetName; + } elseif (!str_starts_with($target, 'php::')) { + $target = 'php::fn::' . $target; + } + + $argTypeStr = $config['args'] ?? ($refInfo['args'] ?? ''); + $defaults = $config['defaults'] ?? []; + + $rawArgs = $this->mergeConstArgs( + $this->buildReceiverArgs($receiver, $args, $receiverPos), + $constArgs + ); + + if ($argTypeStr !== '') { + $types = explode('_', $argTypeStr); + foreach ($types as $i => $type) { + if (!isset($rawArgs[$i])) { + if (($type[0] ?? '') === self::ARG_OPTIONAL && isset($defaults[$i])) { + continue; + } + return null; + } + $rawArgs[$i] = $this->applyArgConversion($rawArgs[$i], $type); + } + } + + return $target . '(' . implode(', ', $rawArgs) . ')'; + } + /** * @param int $receiverPos Position of the receiver in the final argument list. * 0 (default) = receiver first. Non-zero values are 1-indexed (1 = before 1st user arg, @@ -701,23 +778,18 @@ trait UniversalMethodCall */ protected function genUniversalPhpFn(string $receiver, string $phpFunc, array $args, int $receiverPos = 0, array $constArgs = []): string { - $argExprs = $this->buildReceiverArgs($receiver, $args, $receiverPos); - - if ($constArgs) { - $maxPos = max(array_keys($constArgs)); - $totalSlots = max(count($argExprs) + count($constArgs), $maxPos + 1); - $final = []; - $regIdx = 0; - for ($i = 0; $i < $totalSlots; $i++) { - if (isset($constArgs[$i])) { - $final[] = $constArgs[$i]; - } else { - $final[] = $argExprs[$regIdx++]; - } - } - $argExprs = $final; + // Try direct C++ call with type conversions first + $optimized = $this->tryOptimizePhpFn($receiver, $phpFunc, $args, $receiverPos, $constArgs); + if ($optimized !== null) { + return $optimized; } + // Fall back to dynamic php::call() + $argExprs = $this->mergeConstArgs( + $this->buildReceiverArgs($receiver, $args, $receiverPos), + $constArgs + ); + return 'php::call(' . $this->getFuncPtr($phpFunc) . ', php::ArgList{' . implode(', ', $argExprs) . '})'; }