public override object VisitGlobalFunctionCall(BoundGlobalFunctionCall x)
        {
            // TODO: extensible, dictionary of functions ?

            if (x.Name.NameValue == NameUtils.SpecialNames.dirname)
            {
                // dirname( __FILE__ ) -> __DIR__
                if (x.ArgumentsInSourceOrder.Length == 1 &&
                    x.ArgumentsInSourceOrder[0].Value is BoundPseudoConst pc &&
                    pc.ConstType == Ast.PseudoConstUse.Types.File)
                {
                    TransformationCount++;
                    return(new BoundPseudoConst(Ast.PseudoConstUse.Types.Dir).WithAccess(x.Access));
                }
            }
            else if (x.Name.NameValue == NameUtils.SpecialNames.basename)
            {
                // basename( __FILE__ ) -> "filename"
                if (x.ArgumentsInSourceOrder.Length == 1 &&
                    x.ArgumentsInSourceOrder[0].Value is BoundPseudoConst pc &&
                    pc.ConstType == Ast.PseudoConstUse.Types.File)
                {
                    TransformationCount++;
                    var fname = _routine.ContainingFile.FileName;
                    return(new BoundLiteral(fname)
                    {
                        ConstantValue = new Optional <object>(fname)
                    }.WithContext(x));
                }
            }
            else if (x.Name.NameValue == NameUtils.SpecialNames.get_parent_class)
            {
                bool TryResolveParentClassInCurrentClassContext(SourceRoutineSymbol routine, out BoundLiteral newExpression)
                {
                    // in global function, always FALSE
                    if (routine is SourceFunctionSymbol)
                    {
                        // FALSE
                        newExpression = new BoundLiteral(false.AsObject());
                        return(true);
                    }

                    // in a method, we can resolve in compile time:
                    if (routine is SourceMethodSymbol m && m.ContainingType is SourceTypeSymbol t && !t.IsTrait)
                    {
                        if (t.BaseType == null || t.BaseType.IsObjectType())
                        {
                            // FALSE
                            newExpression = new BoundLiteral(false.AsObject())
                            {
                                ConstantValue = false.AsOptional()
                            };
                            return(true);
                        }
                        else
                        {
                            // {class name}
                            var baseTypeName = t.BaseType.PhpQualifiedName().ToString();
                            newExpression = new BoundLiteral(baseTypeName)
                            {
                                ConstantValue = baseTypeName
                            };
                            return(true);
                        }
                    }

                    //
                    newExpression = default;
                    return(false);
                }

                // get_parent_class() -> {class name} | FALSE
                if (x.ArgumentsInSourceOrder.Length == 0)
                {
                    if (TryResolveParentClassInCurrentClassContext(_routine, out var newExpression))
                    {
                        TransformationCount++;
                        return(newExpression.WithContext(x));
                    }
                }

                // get_parent_class( ??? ) -> parent::class | FALSE
                if (x.ArgumentsInSourceOrder.Length == 1)
                {
                    // get_parent_class($this), get_parent_class(__CLASS__) ->  {class name} | FALSE
                    if ((x.ArgumentsInSourceOrder[0].Value is BoundVariableRef varref && varref.Variable is ThisVariableReference) ||
                        (x.ArgumentsInSourceOrder[0].Value is BoundPseudoConst pc && pc.ConstType == Ast.PseudoConstUse.Types.Class))
                    {
                        if (TryResolveParentClassInCurrentClassContext(_routine, out var newExpression))
                        {
                            TransformationCount++;
                            return(newExpression.WithContext(x));
                        }
                    }
                }
            }
            else if (   // method_exists(class_name, method_name) -> FALSE
                x.Name.NameValue == NameUtils.SpecialNames.method_exists &&
                x.ArgumentsInSourceOrder.Length == 2)
            {
                // method_exists(FALSE, ...) -> FALSE
                var value = x.ArgumentsInSourceOrder[0].Value.ConstantValue;
                if (value.HasValue && value.TryConvertToBool(out var bvalue) && !bvalue)
                {
                    TransformationCount++;
                    return(new BoundLiteral(false.AsObject())
                    {
                        ConstantValue = false.AsOptional()
                    }.WithContext(x));
                }
            }
            else if (   // ini_get( {svalue} ) : string|FALSE
                x.Name.NameValue == NameUtils.SpecialNames.ini_get &&
                x.ArgumentsInSourceOrder.Length == 1 &&
                x.ArgumentsInSourceOrder[0].Value.ConstantValue.TryConvertToString(out var svalue))
            {
                // options we're not supporting for sure
                // always FALSE
                if (svalue.StartsWith("xdebug.") || svalue.StartsWith("xcache.") || svalue.StartsWith("opcache.") || svalue.StartsWith("apc."))
                {
                    TransformationCount++;
                    return(new BoundLiteral(false.AsObject())
                    {
                        ConstantValue = false.AsOptional()
                    }.WithContext(x));

                    // TODO: well-known ini options can be translated to access the configuration property directly
                }
            }
            else if (   // extension_loaded( {ext_name} ) : bool
                x.Name.NameValue == NameUtils.SpecialNames.extension_loaded &&
                x.ArgumentsInSourceOrder.Length == 1 &&
                x.ArgumentsInSourceOrder[0].Value.ConstantValue.TryConvertToString(out var ext_name))
            {
                TransformationCount++;

                bool hasextension = DeclaringCompilation
                                    .GlobalSemantics
                                    .Extensions
                                    .Contains(ext_name, StringComparer.OrdinalIgnoreCase);

                // CONSIDER: only when hasextension == True ? Can we add extensions in runtime ?

                Trace.WriteLine($"'extension_loaded({ext_name})' evaluated to {hasextension}.");

                return(new BoundLiteral(hasextension.AsObject())
                {
                    ConstantValue = hasextension.AsOptional()
                }.WithContext(x));
            }

            //
            return(base.VisitGlobalFunctionCall(x));
        }