Migrate setRefKind assignability checker into SkSLAnalysis.

This will allow the inliner to use IsAssignable.

Change-Id: Ic94f71002779b53d0b3dc97f37fbe4bb98b026d8
Bug: skia:10756
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/319414
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index 4114803..61d39ba 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -110,7 +110,6 @@
   "$_tests/sksl/errors/SwitchDuplicateCase.sksl",
   "$_tests/sksl/errors/SwitchTypes.sksl",
   "$_tests/sksl/errors/SwizzleConstantOutput.sksl",
-  "$_tests/sksl/errors/SwizzleDuplicateOutput.sksl",
   "$_tests/sksl/errors/SwizzleMatrix.sksl",
   "$_tests/sksl/errors/SwizzleOnlyLiterals.sksl",
   "$_tests/sksl/errors/SwizzleOutOfBounds.sksl",
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index 87647c7..ef6ed59 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -8,6 +8,7 @@
 #include "src/sksl/SkSLAnalysis.h"
 
 #include "include/private/SkSLSampleUsage.h"
+#include "src/sksl/SkSLErrorReporter.h"
 #include "src/sksl/ir/SkSLExpression.h"
 #include "src/sksl/ir/SkSLProgram.h"
 #include "src/sksl/ir/SkSLProgramElement.h"
@@ -206,6 +207,91 @@
     using INHERITED = ProgramVisitor;
 };
 
+// This isn't actually using ProgramVisitor, because it only considers a subset of the fields for
+// any given expression kind. For instance, when indexing an array (e.g. `x[1]`), we only want to
+// know if the base (`x`) is assignable; the index expression (`1`) doesn't need to be.
+class IsAssignableVisitor {
+public:
+    IsAssignableVisitor(std::vector<VariableReference*>* assignableVars, ErrorReporter& errors)
+        : fAssignableVars(assignableVars)
+        , fErrors(errors) {}
+
+    bool visit(Expression& expr) {
+        this->visitExpression(expr);
+        return fErrors.errorCount() == 0;
+    }
+
+    void visitExpression(Expression& expr) {
+        switch (expr.kind()) {
+            case Expression::Kind::kVariableReference: {
+                VariableReference& varRef = expr.as<VariableReference>();
+                const Variable* var = varRef.fVariable;
+                if (var->fModifiers.fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag |
+                                              Modifiers::kVarying_Flag)) {
+                    fErrors.error(expr.fOffset,
+                                  "cannot modify immutable variable '" + var->fName + "'");
+                } else if (fAssignableVars) {
+                    fAssignableVars->push_back(&varRef);
+                }
+                break;
+            }
+            case Expression::Kind::kFieldAccess:
+                this->visitExpression(*expr.as<FieldAccess>().fBase);
+                break;
+
+            case Expression::Kind::kSwizzle: {
+                const Swizzle& swizzle = expr.as<Swizzle>();
+                this->checkSwizzleWrite(swizzle);
+                this->visitExpression(*swizzle.fBase);
+                break;
+            }
+            case Expression::Kind::kIndex:
+                this->visitExpression(*expr.as<IndexExpression>().fBase);
+                break;
+
+            case Expression::Kind::kTernary: {
+                const TernaryExpression& ternary = expr.as<TernaryExpression>();
+                this->visitExpression(*ternary.fIfTrue);
+                this->visitExpression(*ternary.fIfFalse);
+                break;
+            }
+            case Expression::Kind::kExternalValue: {
+                const ExternalValue* var = expr.as<ExternalValueReference>().fValue;
+                if (!var->canWrite()) {
+                    fErrors.error(expr.fOffset,
+                                  "cannot modify immutable external value '" + var->fName + "'");
+                }
+                break;
+            }
+            default:
+                fErrors.error(expr.fOffset, "cannot assign to this expression");
+                break;
+        }
+    }
+
+private:
+    void checkSwizzleWrite(const Swizzle& swizzle) {
+        int bits = 0;
+        for (int idx : swizzle.fComponents) {
+            SkASSERT(idx <= 3);
+            int bit = 1 << idx;
+            if (bits & bit) {
+                fErrors.error(swizzle.fOffset,
+                              "cannot write to the same swizzle field more than once");
+                break;
+            }
+            bits |= bit;
+        }
+    }
+
+    std::vector<VariableReference*>* fAssignableVars;
+    ErrorReporter& fErrors;
+
+    using INHERITED = ProgramVisitor;
+};
+
+
+
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -237,6 +323,11 @@
     return VariableWriteVisitor(&var).visit(stmt);
 }
 
+bool Analysis::IsAssignable(Expression& expr, std::vector<VariableReference*>* assignableVars,
+                            ErrorReporter& errors) {
+    return IsAssignableVisitor{assignableVars, errors}.visit(expr);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // ProgramVisitor
 
diff --git a/src/sksl/SkSLAnalysis.h b/src/sksl/SkSLAnalysis.h
index eedb337..1a627c2 100644
--- a/src/sksl/SkSLAnalysis.h
+++ b/src/sksl/SkSLAnalysis.h
@@ -8,17 +8,21 @@
 #ifndef SkSLAnalysis_DEFINED
 #define SkSLAnalysis_DEFINED
 
+#include <vector>
+
 #include "include/private/SkSLSampleUsage.h"
 #include "src/sksl/SkSLDefines.h"
 
 namespace SkSL {
 
+class ErrorReporter;
 struct Expression;
 struct FunctionDefinition;
 struct Program;
 struct ProgramElement;
 struct Statement;
 struct Variable;
+struct VariableReference;
 
 /**
  * Provides utilities for analyzing SkSL statically before it's composed into a full program.
@@ -34,6 +38,8 @@
     static int NodeCount(const FunctionDefinition& function);
 
     static bool StatementWritesToVariable(const Statement& stmt, const Variable& var);
+    static bool IsAssignable(Expression& expr, std::vector<VariableReference*>* assignableVars,
+                             ErrorReporter& errors);
 };
 
 /**
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index a7a0882..f92c6b3 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -2810,60 +2810,17 @@
     }
 }
 
-bool IRGenerator::checkSwizzleWrite(const Swizzle& swizzle) {
-    int bits = 0;
-    for (int idx : swizzle.fComponents) {
-        SkASSERT(idx <= 3);
-        int bit = 1 << idx;
-        if (bits & bit) {
-            fErrors.error(swizzle.fOffset,
-                          "cannot write to the same swizzle field more than once");
-            return false;
-        }
-        bits |= bit;
+bool IRGenerator::setRefKind(Expression& expr, VariableReference::RefKind kind) {
+    std::vector<VariableReference*> assignableVars;
+    if (!Analysis::IsAssignable(expr, &assignableVars, fErrors)) {
+        return false;
+    }
+    for (VariableReference* v : assignableVars) {
+        v->setRefKind(kind);
     }
     return true;
 }
 
-bool IRGenerator::setRefKind(Expression& expr, VariableReference::RefKind kind) {
-    switch (expr.kind()) {
-        case Expression::Kind::kVariableReference: {
-            const Variable& var = *expr.as<VariableReference>().fVariable;
-            if (var.fModifiers.fFlags &
-                (Modifiers::kConst_Flag | Modifiers::kUniform_Flag | Modifiers::kVarying_Flag)) {
-                fErrors.error(expr.fOffset, "cannot modify immutable variable '" + var.fName + "'");
-                return false;
-            }
-            expr.as<VariableReference>().setRefKind(kind);
-            return true;
-        }
-        case Expression::Kind::kFieldAccess:
-            return this->setRefKind(*expr.as<FieldAccess>().fBase, kind);
-        case Expression::Kind::kSwizzle: {
-            const Swizzle& swizzle = expr.as<Swizzle>();
-            return this->checkSwizzleWrite(swizzle) && this->setRefKind(*swizzle.fBase, kind);
-        }
-        case Expression::Kind::kIndex:
-            return this->setRefKind(*expr.as<IndexExpression>().fBase, kind);
-        case Expression::Kind::kTernary: {
-            const TernaryExpression& t = expr.as<TernaryExpression>();
-            return this->setRefKind(*t.fIfTrue, kind) && this->setRefKind(*t.fIfFalse, kind);
-        }
-        case Expression::Kind::kExternalValue: {
-            const ExternalValue& v = *expr.as<ExternalValueReference>().fValue;
-            if (!v.canWrite()) {
-                fErrors.error(expr.fOffset,
-                              "cannot modify immutable external value '" + v.fName + "'");
-                return false;
-            }
-            return true;
-        }
-        default:
-            fErrors.error(expr.fOffset, "cannot assign to this expression");
-            return false;
-    }
-}
-
 void IRGenerator::convertProgram(Program::Kind kind,
                                  const char* text,
                                  size_t length,
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index 1a0a928..a19947f 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -198,7 +198,6 @@
     void checkValid(const Expression& expr);
     bool setRefKind(Expression& expr, VariableReference::RefKind kind);
     bool getConstantInt(const Expression& value, int64_t* out);
-    bool checkSwizzleWrite(const Swizzle& swizzle);
     void copyIntrinsicIfNeeded(const FunctionDeclaration& function);
 
     Inliner* fInliner = nullptr;
diff --git a/tests/sksl/errors/InvalidAssignment.sksl b/tests/sksl/errors/InvalidAssignment.sksl
index 064988b..8e95fa7 100644
--- a/tests/sksl/errors/InvalidAssignment.sksl
+++ b/tests/sksl/errors/InvalidAssignment.sksl
@@ -1,6 +1,19 @@
-uniform int a;
-const int b;
+struct S  {
+    float f;
+};
 
-void assign_to_literal() { 1 = 2; }
-void assign_to_uniform() { a = 0; }
-void assign_to_const()   { b = 0; }
+uniform int u;
+
+void assign_to_literal()             { 1 = 2; }
+void assign_to_uniform()             { u = 0; }
+void assign_to_const()               { const int x; x = 0; }
+void assign_to_const_array()         { const int x[1]; x[0] = 0; }
+void assign_to_const_swizzle()       { const half4 x; x.w = 0; }
+void assign_to_repeated_swizzle()    { half4 x; x.yy = half2(0); }
+void assign_to_const_struct()        { const S s; s.f = 0; }
+void assign_to_ternary()             { float l; float r; (true ? l : r) = 0; }  // TODO(skbug.com/10767)
+void assign_to_ternary_const_left()  { const float l; float r; (true ? l : r) = 0; }
+void assign_to_ternary_const_right() { float l; const float r; (false ? l : r) = 0; }
+void assign_to_ternary_const_both()  { const float l; const float r; (true ? l : r) = 0; }
+void assign_to_unary_minus()         { float x; -x = 0; }
+void assign_to_unary_plus()          { float x; +x = 0; }  // TODO(skbug.com/10766)
diff --git a/tests/sksl/errors/SwizzleDuplicateOutput.sksl b/tests/sksl/errors/SwizzleDuplicateOutput.sksl
deleted file mode 100644
index 37e2e0a..0000000
--- a/tests/sksl/errors/SwizzleDuplicateOutput.sksl
+++ /dev/null
@@ -1,4 +0,0 @@
-void main() {
-    float4 test = float4(1);
-    test.xyyz = float4(1);
-}
diff --git a/tests/sksl/errors/golden/InvalidAssignment.glsl b/tests/sksl/errors/golden/InvalidAssignment.glsl
index f7fcc0e..1bb4da4 100644
--- a/tests/sksl/errors/golden/InvalidAssignment.glsl
+++ b/tests/sksl/errors/golden/InvalidAssignment.glsl
@@ -1,6 +1,14 @@
 ### Compilation failed:
 
-error: 4: cannot assign to this expression
-error: 5: cannot modify immutable variable 'a'
-error: 6: cannot modify immutable variable 'b'
-3 errors
+error: 7: cannot assign to this expression
+error: 8: cannot modify immutable variable 'u'
+error: 9: cannot modify immutable variable 'x'
+error: 10: cannot modify immutable variable 'x'
+error: 11: cannot modify immutable variable 'x'
+error: 12: cannot write to the same swizzle field more than once
+error: 13: cannot modify immutable variable 's'
+error: 15: cannot modify immutable variable 'l'
+error: 16: cannot modify immutable variable 'r'
+error: 17: cannot modify immutable variable 'l'
+error: 18: cannot assign to this expression
+11 errors
diff --git a/tests/sksl/errors/golden/SwizzleDuplicateOutput.glsl b/tests/sksl/errors/golden/SwizzleDuplicateOutput.glsl
deleted file mode 100644
index 03bed6e..0000000
--- a/tests/sksl/errors/golden/SwizzleDuplicateOutput.glsl
+++ /dev/null
@@ -1,4 +0,0 @@
-### Compilation failed:
-
-error: 3: cannot write to the same swizzle field more than once
-1 error