void VarDeclaration::ErrorCheck()

in src/sksl/ir/SkSLVarDeclarations.cpp [96:293]


void VarDeclaration::ErrorCheck(const Context& context,
                                Position pos,
                                Position modifiersPosition,
                                const Layout& layout,
                                ModifierFlags modifierFlags,
                                const Type* type,
                                const Type* baseType,
                                Variable::Storage storage) {
    SkASSERT(type->isArray() ? baseType->matches(type->componentType())
                             : baseType->matches(*type));

    if (baseType->componentType().isOpaque() && !baseType->componentType().isAtomic() &&
        storage != Variable::Storage::kGlobal) {
        context.fErrors->error(pos, "variables of type '" + baseType->displayName() +
                                    "' must be global");
    }
    if ((modifierFlags & ModifierFlag::kIn) && baseType->isMatrix()) {
        context.fErrors->error(pos, "'in' variables may not have matrix type");
    }
    if ((modifierFlags & ModifierFlag::kIn) && type->isUnsizedArray()) {
        context.fErrors->error(pos, "'in' variables may not have unsized array type");
    }
    if ((modifierFlags & ModifierFlag::kOut) && type->isUnsizedArray()) {
        context.fErrors->error(pos, "'out' variables may not have unsized array type");
    }
    if ((modifierFlags & ModifierFlag::kIn) && modifierFlags.isUniform()) {
        context.fErrors->error(pos, "'in uniform' variables not permitted");
    }
    if (modifierFlags.isReadOnly() && modifierFlags.isWriteOnly()) {
        context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined");
    }
    if (modifierFlags.isUniform() && modifierFlags.isBuffer()) {
        context.fErrors->error(pos, "'uniform buffer' variables not permitted");
    }
    if (modifierFlags.isWorkgroup() && (modifierFlags & (ModifierFlag::kIn |
                                                         ModifierFlag::kOut))) {
        context.fErrors->error(pos, "in / out variables may not be declared workgroup");
    }
    if (modifierFlags.isUniform()) {
        check_valid_uniform_type(pos, baseType, context);
    }
    if (baseType->isEffectChild() && !modifierFlags.isUniform()) {
        context.fErrors->error(pos, "variables of type '" + baseType->displayName() +
                                    "' must be uniform");
    }
    if (baseType->isEffectChild() && context.fConfig->fKind == ProgramKind::kMeshVertex) {
        context.fErrors->error(pos, "effects are not permitted in mesh vertex shaders");
    }
    if (baseType->isOrContainsAtomic()) {
        // An atomic variable (or a struct or an array that contains an atomic member) must be
        // either:
        //   a. Declared as a workgroup-shared variable, OR
        //   b. Declared as the member of writable storage buffer block (i.e. has no readonly
        //   restriction).
        //
        // The checks below will enforce these two rules on all declarations. If the variable is not
        // declared with the workgroup modifier, then it must be declared in the interface block
        // storage. If this is the declaration for an interface block that contains an atomic
        // member, then it must have the `buffer` modifier and no `readonly` modifier.
        bool isBlockMember = (storage == Variable::Storage::kInterfaceBlock);
        bool isWritableStorageBuffer = modifierFlags.isBuffer() && !modifierFlags.isReadOnly();

        if (!modifierFlags.isWorkgroup() &&
            !(baseType->isInterfaceBlock() ? isWritableStorageBuffer : isBlockMember)) {
            context.fErrors->error(pos, "atomics are only permitted in workgroup variables and "
                                        "writable storage blocks");
        }
    }
    if (layout.fFlags & LayoutFlag::kColor) {
        if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
            context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects");
        }
        if (!modifierFlags.isUniform()) {
            context.fErrors->error(pos, "'layout(color)' is only permitted on 'uniform' variables");
        }
        auto validColorXformType = [](const Type& t) {
            return t.isVector() && t.componentType().isFloat() &&
                   (t.columns() == 3 || t.columns() == 4);
        };
        if (!validColorXformType(*baseType)) {
            context.fErrors->error(pos, "'layout(color)' is not permitted on variables of type '" +
                                        baseType->displayName() + "'");
        }
    }

    ModifierFlags permitted = ModifierFlag::kConst | ModifierFlag::kHighp | ModifierFlag::kMediump |
                              ModifierFlag::kLowp;
    if (storage == Variable::Storage::kGlobal) {
        // Uniforms are allowed in all programs
        permitted |= ModifierFlag::kUniform;

        // No other modifiers are allowed in runtime effects.
        if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
            if (baseType->isInterfaceBlock()) {
                // Interface blocks allow `buffer`.
                permitted |= ModifierFlag::kBuffer;

                if (modifierFlags.isBuffer()) {
                    // Only storage blocks allow `readonly` and `writeonly`.
                    // (`readonly` and `writeonly` textures are converted to separate types via
                    // applyAccessQualifiers.)
                    permitted |= ModifierFlag::kReadOnly | ModifierFlag::kWriteOnly;
                }

                // It is an error for an unsized array to appear anywhere but the last member of a
                // "buffer" block.
                const auto& fields = baseType->fields();
                const int illegalRangeEnd = SkToInt(fields.size()) -
                                            (modifierFlags.isBuffer() ? 1 : 0);
                for (int i = 0; i < illegalRangeEnd; ++i) {
                    if (fields[i].fType->isUnsizedArray()) {
                        context.fErrors->error(
                                fields[i].fPosition,
                                "unsized array must be the last member of a storage block");
                    }
                }
            }

            if (!baseType->isOpaque()) {
                // Only non-opaque types allow `in` and `out`.
                permitted |= ModifierFlag::kIn | ModifierFlag::kOut;
            }
            if (ProgramConfig::IsFragment(context.fConfig->fKind) && baseType->isStruct() &&
                !baseType->isInterfaceBlock()) {
                // Only structs in fragment shaders allow `pixel_local`.
                permitted |= ModifierFlag::kPixelLocal;
            }
            if (ProgramConfig::IsCompute(context.fConfig->fKind)) {
                // Only compute shaders allow `workgroup`.
                if (!baseType->isOpaque() || baseType->isAtomic()) {
                    permitted |= ModifierFlag::kWorkgroup;
                }
            } else {
                // Only vertex/fragment shaders allow `flat` and `noperspective`.
                permitted |= ModifierFlag::kFlat | ModifierFlag::kNoPerspective;
            }
        }
    }

    LayoutFlags permittedLayoutFlags = LayoutFlag::kAll;

    // Pixel format modifiers are required on storage textures, and forbidden on other types.
    if (baseType->isStorageTexture()) {
        if (!(layout.fFlags & LayoutFlag::kAllPixelFormats)) {
            context.fErrors->error(pos, "storage textures must declare a pixel format");
        }
    } else {
        permittedLayoutFlags &= ~LayoutFlag::kAllPixelFormats;
    }

    // The `texture` and `sampler` modifiers can be present respectively on a texture and sampler or
    // simultaneously on a combined image-sampler but they are not permitted on any other type.
    switch (baseType->typeKind()) {
        case Type::TypeKind::kSampler:
            // Both texture and sampler flags are permitted
            break;
        case Type::TypeKind::kTexture:
            permittedLayoutFlags &= ~LayoutFlag::kSampler;
            break;
        case Type::TypeKind::kSeparateSampler:
            permittedLayoutFlags &= ~LayoutFlag::kTexture;
            break;
        default:
            permittedLayoutFlags &= ~(LayoutFlag::kTexture | LayoutFlag::kSampler);
            break;
    }

    // We don't allow 'binding' or 'set' on normal uniform variables, only on textures, samplers,
    // and interface blocks (holding uniform variables). They're also only allowed at global scope,
    // not on interface block fields (or locals/parameters).
    bool permitBindingAndSet = baseType->typeKind() == Type::TypeKind::kSampler ||
                               baseType->typeKind() == Type::TypeKind::kSeparateSampler ||
                               baseType->typeKind() == Type::TypeKind::kTexture ||
                               baseType->isInterfaceBlock();
    if (storage != Variable::Storage::kGlobal || (modifierFlags.isUniform() &&
                                                  !permitBindingAndSet)) {
        permittedLayoutFlags &= ~LayoutFlag::kBinding;
        permittedLayoutFlags &= ~LayoutFlag::kSet;
        permittedLayoutFlags &= ~LayoutFlag::kAllBackends;
    }
    if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
        // Disallow all layout flags except 'color' in runtime effects
        permittedLayoutFlags &= LayoutFlag::kColor;
    }

    // The `push_constant` flag isn't allowed on in-variables, out-variables, bindings or sets.
    if ((layout.fFlags & (LayoutFlag::kSet | LayoutFlag::kBinding)) ||
        (modifierFlags & (ModifierFlag::kIn | ModifierFlag::kOut))) {
        permittedLayoutFlags &= ~LayoutFlag::kPushConstant;
    }
    // The `builtin` layout flag is only allowed in modules.
    if (!context.fConfig->isBuiltinCode()) {
        permittedLayoutFlags &= ~LayoutFlag::kBuiltin;
    }

    modifierFlags.checkPermittedFlags(context, modifiersPosition, permitted);
    layout.checkPermittedLayout(context, modifiersPosition, permittedLayoutFlags);
}