private static void EnsureMatchingComponents(SSLVisitor vis, IToken token, string name, ShaderType a1c, ShaderType a2c, ShaderType a3c)
 {
     if (!a2c.CanPromoteTo(a1c))
     {
         vis.Error(token, $"The built-in function '{name}' requires a '{a1c}' compatible type for argument 2 (actual is '{a2c}').");
     }
     if ((a3c != ShaderType.Void) && !a3c.CanPromoteTo(a1c))
     {
         vis.Error(token, $"The built-in function '{name}' requires a '{a1c}' compatible type for argument 3 (actual is '{a3c}').");
     }
 }
        // Checks the arugments and return types for built in functions
        // Note: the visitor ensures that the correct number of args are present, we dont need to check that in this function
        public static ShaderType CheckBuiltinCall(SSLVisitor vis, IToken token, string name, int type, ExprResult[] args)
        {
            var btidx = Array.FindIndex(args, a => a.IsArray);

            if (btidx != -1)
            {
                vis.Error(token, $"Arguments to built-in functions cannot be arrays (arg {btidx+1}).");
            }

            ShaderType a1t = args[0].Type,
                       a2t = (args.Length > 1) ? args[1].Type : ShaderType.Void,
                       a3t = (args.Length > 2) ? args[2].Type : ShaderType.Void;
            ShaderType a1c = a1t.GetComponentType(),
                       a2c = a2t.GetComponentType(),
                       a3c = a3t.GetComponentType();

            if (type >= SSLParser.BIF_TEXSIZE && type <= SSLParser.BIF_TEXFETCH)             // Functions that deal with texture handles
            {
                if (!a1t.IsTextureType())
                {
                    vis.Error(token, $"The built-in function '{name}' requires a texture handle as argument 1.");
                }
                var tdim = a1t.GetTexelDim();

                if (type == SSLParser.BIF_TEXSIZE)
                {
                    return(ShaderType.Int.ToVectorType(tdim).Value);
                }
                else if (type == SSLParser.BIF_TEXTURE)
                {
                    if (!a2c.CanPromoteTo(ShaderType.Float))
                    {
                        vis.Error(token, $"The built-in function '{name}' requires a floating-point vector or scalar as argument 2.");
                    }
                    if (a2t.GetComponentCount() != tdim)
                    {
                        vis.Error(token, "The size of the texture access coordinates does not match the size of the texture.");
                    }
                }
                else
                {
                    if (a2c != ShaderType.Int)
                    {
                        vis.Error(token, $"The built-in function '{name}' requires integer texture coordinates.");
                    }
                    if (a2t.GetComponentCount() != tdim)
                    {
                        vis.Error(token, "The size of the texture access coordinates does not match the size of the texture.");
                    }
                }
                return(ShaderType.Float4);
            }
            else if (type >= SSLParser.BIF_IMAGESIZE && type <= SSLParser.BIF_IMAGESTORE)             // Functions that deal with image handles
            {
                if (!a1t.IsImageType())
                {
                    vis.Error(token, $"The built-in function '{name}' requires an image handle as argument 1.");
                }
                var tdim = a1t.GetTexelDim();

                if (type == SSLParser.BIF_IMAGESIZE)
                {
                    return(ShaderType.Int.ToVectorType(tdim).Value);
                }

                if (a2c != ShaderType.Int)
                {
                    vis.Error(token, $"The built-in function '{name}' requires integer texture coordinates.");
                }
                if (a2t.GetComponentCount() != tdim)
                {
                    vis.Error(token, "The size of the texture access coordinates does not match the size of the texture.");
                }

                var ttype = args[0].ImageFormat.Value;
                if ((type == SSLParser.BIF_IMAGESTORE) && !a3t.CanPromoteTo(ttype.GetTexelType()))
                {
                    vis.Error(token, $"The built-in function '{name}' requires the type {ttype.GetTexelType()} for argument 3.");
                }
                return((type == SSLParser.BIF_IMAGESTORE) ? ShaderType.Void : ttype.GetTexelType());
            }
            else if (type >= SSLParser.BIF_MATCOMPMUL && type <= SSLParser.BIF_DETERMINANT)             // Functions that deal with matrices
            {
                btidx = Array.FindIndex(args, a => !a.Type.IsMatrixType());
                if (btidx != -1)
                {
                    vis.Error(token, $"The built-in function '{name}' does not support non-matrix arguments (arg {btidx+1}).");
                }

                if (type == SSLParser.BIF_MATCOMPMUL)                 // 'matCompMul' function
                {
                    if (a1t != a2t)
                    {
                        vis.Error(token, $"The built-in function '{name}' expects two identically sized matrix types for both arguments.");
                    }
                    return(a1t);
                }
                else if (type == SSLParser.BIF_TRANSPOSE || type == SSLParser.BIF_INVERSE)                 // 'transpose' and 'inverse' functions
                {
                    return(a1t);
                }
                else if (type == SSLParser.BIF_DETERMINANT)                 // 'determinant' function
                {
                    return(ShaderType.Float);
                }
            }
            else if (type == SSLParser.BIF_SUBPASSLOAD)             // 'subpassLoad' function
            {
                if (!a1t.IsSubpassInput())
                {
                    vis.Error(token, "The 'subpassLoad' function requires a subpass input as an argument.");
                }
                return(ShaderType.Float4);
            }
            else             // Functions that deal with scalar and vector types
            {
                btidx = Array.FindIndex(args, a => !(a.Type.IsScalarType() || a.Type.IsVectorType()));
                if (btidx != -1)
                {
                    vis.Error(token, $"The built-in function '{name}' does not support handle or matrix arguments (arg {btidx+1}).");
                }

                if (type >= SSLParser.BIF_DEG2RAD && type <= SSLParser.BIF_FRACT)                 // trig, exponential, and 1-arg common functions
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_MOD)                 // 'mod' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureSizeIfNotScalar(vis, token, name, a1t, a2t, 1, 2);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_MIN || type == SSLParser.BIF_MAX)                 // 'min' and 'max' functions
                {
                    EnsureCastableComponents(vis, token, name, a1c, ShaderType.Void, ShaderType.Void, ShaderType.Float);
                    EnsureMatchingComponents(vis, token, name, a1c, a2c, a3c);
                    EnsureSizeIfNotScalar(vis, token, name, a1t, a2t, 1, 2);
                    return(a1t);
                }
                else if (type == SSLParser.BIF_CLAMP)                 // 'clamp' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, ShaderType.Void, ShaderType.Void, ShaderType.Float);
                    EnsureMatchingComponents(vis, token, name, a1c, a2c, a3c);
                    EnsureVectorSizes(vis, token, name, ShaderType.Void, a2t, a3t);
                    EnsureSizeIfNotScalar(vis, token, name, a1t, a2t, 1, 2);
                    return(a1t);
                }
                else if (type == SSLParser.BIF_MIX)                 // 'mix' function, unlike GLSL this only supports floating points
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, ShaderType.Void);
                    EnsureSizeIfNotScalar(vis, token, name, a1t, a3t, 1, 3);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_STEP)                 // 'step' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureSizeIfNotScalar(vis, token, name, a2t, a1t, 2, 1);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_SSTEP)                 // 'smoothstep' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    EnsureSizeIfNotScalar(vis, token, name, a2t, a1t, 2, 1);
                    return(ShaderType.Float.ToVectorType(a3t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_LENGTH)                 // 'length' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    return(ShaderType.Float);
                }
                else if (type == SSLParser.BIF_DISTANCE || type == SSLParser.BIF_DOT)                 // 'distance' and 'dot' functions
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    return(ShaderType.Float);
                }
                else if (type == SSLParser.BIF_CROSS)                 // 'cross' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    if (a1t.GetComponentCount() != 3 || a2t.GetComponentCount() != 3)
                    {
                        vis.Error(token, $"The built-in function '{name}' expects 3-component floating-point vector arguments.");
                    }
                    return(ShaderType.Float3);
                }
                else if (type == SSLParser.BIF_NORMALIZE)                 // 'normalize' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_FFORWARD)                 // 'faceforward' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_REFLECT)                 // 'reflect' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_REFRACT)                 // 'refract' function
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, ShaderType.Void);
                    if (!a3t.CanPromoteTo(ShaderType.Float))
                    {
                        vis.Error(token, $"The built-in function '{name}' expects a floating-point scalar for argument 3.");
                    }
                    return(ShaderType.Float.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type >= SSLParser.BIF_VECLT && type <= SSLParser.BIF_VECGE)                 // 2-arg relational vector functions
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Float);
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    return(ShaderType.Bool.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type == SSLParser.BIF_VECEQ || type == SSLParser.BIF_VECNE)                 // 2-arg logical vector functions
                {
                    if ((a1c == ShaderType.Bool) || a1c.CanPromoteTo(ShaderType.Float))
                    {
                        EnsureMatchingComponents(vis, token, name, a1c, a2c, a3c);
                    }
                    else
                    {
                        vis.Error(token, $"The built-in function '{name}' expects floating-point or boolean vectors for all arguments.");
                    }
                    EnsureVectorSizes(vis, token, name, a1t, a2t, a3t);
                    return(ShaderType.Bool.ToVectorType(a1t.GetComponentCount()).Value);
                }
                else if (type >= SSLParser.BIF_VECANY && type <= SSLParser.BIF_VECNOT)                 // 1-arg logical vector functions
                {
                    EnsureCastableComponents(vis, token, name, a1c, a2c, a3c, ShaderType.Bool);
                    return(ShaderType.Bool);
                }
            }

            // Error
            vis.Error(token, $"The built-in function '{name}' was not understood.");
            return(ShaderType.Void);
        }