diff --git a/Test/baseResults/hlsl.aliasOpaque.frag.out b/Test/baseResults/hlsl.aliasOpaque.frag.out
new file mode 100755
index 0000000000000000000000000000000000000000..370dcb1aa8007fd180300f7a65f4db80a18f65ca
--- /dev/null
+++ b/Test/baseResults/hlsl.aliasOpaque.frag.out
@@ -0,0 +1,174 @@
+hlsl.aliasOpaque.frag
+Shader version: 500
+gl_FragCoord origin is upper left
+0:? Sequence
+0:12  Function Definition: osCall(struct-OS-p1-f1-t211; ( temp 4-component vector of float)
+0:12    Function Parameters: 
+0:?       'ss' ( in sampler)
+0:?       'a' ( in float)
+0:?       'tex' ( in texture2D)
+0:?     Sequence
+0:13      Branch: Return with expression
+0:13        vector-scale ( temp 4-component vector of float)
+0:?           'a' ( in float)
+0:13          texture ( temp 4-component vector of float)
+0:13            Construct combined texture-sampler ( temp sampler2D)
+0:?               'tex' ( in texture2D)
+0:?               'ss' ( in sampler)
+0:?             Constant:
+0:?               0.200000
+0:?               0.300000
+0:17  Function Definition: @main( ( temp 4-component vector of float)
+0:17    Function Parameters: 
+0:?     Sequence
+0:19      'gss2' ( uniform sampler)
+0:20      'gss' ( uniform sampler)
+0:21      'gtex' ( uniform texture2D)
+0:22      move second child to first child ( temp float)
+0:?         'a' ( temp float)
+0:22        Constant:
+0:22          3.000000
+0:28      Branch: Return with expression
+0:28        Function Call: osCall(struct-OS-p1-f1-t211; ( temp 4-component vector of float)
+0:?           'gss' ( uniform sampler)
+0:?           'a' ( temp float)
+0:?           'gtex' ( uniform texture2D)
+0:17  Function Definition: main( ( temp void)
+0:17    Function Parameters: 
+0:?     Sequence
+0:17      move second child to first child ( temp 4-component vector of float)
+0:?         '@entryPointOutput' (layout( location=0) out 4-component vector of float)
+0:17        Function Call: @main( ( temp 4-component vector of float)
+0:?   Linker Objects
+0:?     'gss' ( uniform sampler)
+0:?     'gss2' ( uniform sampler)
+0:?     'gtex' ( uniform texture2D)
+0:?     '@entryPointOutput' (layout( location=0) out 4-component vector of float)
+
+
+Linked fragment stage:
+
+
+Shader version: 500
+gl_FragCoord origin is upper left
+0:? Sequence
+0:12  Function Definition: osCall(struct-OS-p1-f1-t211; ( temp 4-component vector of float)
+0:12    Function Parameters: 
+0:?       'ss' ( in sampler)
+0:?       'a' ( in float)
+0:?       'tex' ( in texture2D)
+0:?     Sequence
+0:13      Branch: Return with expression
+0:13        vector-scale ( temp 4-component vector of float)
+0:?           'a' ( in float)
+0:13          texture ( temp 4-component vector of float)
+0:13            Construct combined texture-sampler ( temp sampler2D)
+0:?               'tex' ( in texture2D)
+0:?               'ss' ( in sampler)
+0:?             Constant:
+0:?               0.200000
+0:?               0.300000
+0:17  Function Definition: @main( ( temp 4-component vector of float)
+0:17    Function Parameters: 
+0:?     Sequence
+0:19      'gss2' ( uniform sampler)
+0:20      'gss' ( uniform sampler)
+0:21      'gtex' ( uniform texture2D)
+0:22      move second child to first child ( temp float)
+0:?         'a' ( temp float)
+0:22        Constant:
+0:22          3.000000
+0:28      Branch: Return with expression
+0:28        Function Call: osCall(struct-OS-p1-f1-t211; ( temp 4-component vector of float)
+0:?           'gss' ( uniform sampler)
+0:?           'a' ( temp float)
+0:?           'gtex' ( uniform texture2D)
+0:17  Function Definition: main( ( temp void)
+0:17    Function Parameters: 
+0:?     Sequence
+0:17      move second child to first child ( temp 4-component vector of float)
+0:?         '@entryPointOutput' (layout( location=0) out 4-component vector of float)
+0:17        Function Call: @main( ( temp 4-component vector of float)
+0:?   Linker Objects
+0:?     'gss' ( uniform sampler)
+0:?     'gss2' ( uniform sampler)
+0:?     'gtex' ( uniform texture2D)
+0:?     '@entryPointOutput' (layout( location=0) out 4-component vector of float)
+
+// Module Version 10000
+// Generated by (magic number): 80001
+// Id's are bound by 48
+
+                              Capability Shader
+               1:             ExtInstImport  "GLSL.std.450"
+                              MemoryModel Logical GLSL450
+                              EntryPoint Fragment 4  "main" 46
+                              ExecutionMode 4 OriginUpperLeft
+                              Source HLSL 500
+                              Name 4  "main"
+                              Name 17  "osCall(struct-OS-p1-f1-t211;"
+                              Name 14  "ss"
+                              Name 15  "a"
+                              Name 16  "tex"
+                              Name 20  "@main("
+                              Name 35  "gss2"
+                              Name 36  "gss"
+                              Name 37  "gtex"
+                              Name 38  "a"
+                              Name 40  "param"
+                              Name 46  "@entryPointOutput"
+                              Decorate 35(gss2) DescriptorSet 0
+                              Decorate 36(gss) DescriptorSet 0
+                              Decorate 37(gtex) DescriptorSet 0
+                              Decorate 46(@entryPointOutput) Location 0
+               2:             TypeVoid
+               3:             TypeFunction 2
+               6:             TypeSampler
+               7:             TypePointer UniformConstant 6
+               8:             TypeFloat 32
+               9:             TypePointer Function 8(float)
+              10:             TypeImage 8(float) 2D sampled format:Unknown
+              11:             TypePointer UniformConstant 10
+              12:             TypeVector 8(float) 4
+              13:             TypeFunction 12(fvec4) 7(ptr) 9(ptr) 11(ptr)
+              19:             TypeFunction 12(fvec4)
+              25:             TypeSampledImage 10
+              27:             TypeVector 8(float) 2
+              28:    8(float) Constant 1045220557
+              29:    8(float) Constant 1050253722
+              30:   27(fvec2) ConstantComposite 28 29
+        35(gss2):      7(ptr) Variable UniformConstant
+         36(gss):      7(ptr) Variable UniformConstant
+        37(gtex):     11(ptr) Variable UniformConstant
+              39:    8(float) Constant 1077936128
+              45:             TypePointer Output 12(fvec4)
+46(@entryPointOutput):     45(ptr) Variable Output
+         4(main):           2 Function None 3
+               5:             Label
+              47:   12(fvec4) FunctionCall 20(@main()
+                              Store 46(@entryPointOutput) 47
+                              Return
+                              FunctionEnd
+17(osCall(struct-OS-p1-f1-t211;):   12(fvec4) Function None 13
+          14(ss):      7(ptr) FunctionParameter
+           15(a):      9(ptr) FunctionParameter
+         16(tex):     11(ptr) FunctionParameter
+              18:             Label
+              22:    8(float) Load 15(a)
+              23:          10 Load 16(tex)
+              24:           6 Load 14(ss)
+              26:          25 SampledImage 23 24
+              31:   12(fvec4) ImageSampleImplicitLod 26 30
+              32:   12(fvec4) VectorTimesScalar 31 22
+                              ReturnValue 32
+                              FunctionEnd
+      20(@main():   12(fvec4) Function None 19
+              21:             Label
+           38(a):      9(ptr) Variable Function
+       40(param):      9(ptr) Variable Function
+                              Store 38(a) 39
+              41:    8(float) Load 38(a)
+                              Store 40(param) 41
+              42:   12(fvec4) FunctionCall 17(osCall(struct-OS-p1-f1-t211;) 36(gss) 40(param) 37(gtex)
+                              ReturnValue 42
+                              FunctionEnd
diff --git a/Test/hlsl.aliasOpaque.frag b/Test/hlsl.aliasOpaque.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8b1cceff91f11ae7db6bcdb7853da48269378afc
--- /dev/null
+++ b/Test/hlsl.aliasOpaque.frag
@@ -0,0 +1,29 @@
+struct OS {
+    SamplerState ss;
+    float a;
+    Texture2D tex;
+};
+
+SamplerState gss;
+SamplerState gss2;
+Texture2D gtex;
+
+float4 osCall(OS s)
+{
+    return s.a * s.tex.Sample(s.ss, float2(0.2, 0.3));
+}
+
+float4 main() : SV_TARGET0
+{
+    OS os;
+    os.ss = gss2;
+    os.ss = gss;
+    os.tex = gtex;
+    os.a = 3.0;
+
+    // this should give an error
+    //SamplerState localss;
+    //localss = gss2;
+
+    return osCall(os);
+}
diff --git a/gtests/Hlsl.FromFile.cpp b/gtests/Hlsl.FromFile.cpp
index 91d7ae61a7ad61b05f98170cb91d09f301c0036c..9edc51af7324e989eae30db4745f6147aa6797a1 100644
--- a/gtests/Hlsl.FromFile.cpp
+++ b/gtests/Hlsl.FromFile.cpp
@@ -81,6 +81,7 @@ INSTANTIATE_TEST_CASE_P(
     ToSpirv, HlslCompileTest,
     ::testing::ValuesIn(std::vector<FileNameEntryPointPair>{
         {"hlsl.amend.frag", "f1"},
+        {"hlsl.aliasOpaque.frag", "main"},
         {"hlsl.array.frag", "PixelShaderFunction"},
         {"hlsl.array.implicit-size.frag", "PixelShaderFunction"},
         {"hlsl.array.multidim.frag", "main"},
diff --git a/hlsl/hlslParseHelper.cpp b/hlsl/hlslParseHelper.cpp
index 43949b51b9c1b01561a9f46445a2eb13ec48f92a..ff0c7771e1c904178d3f00f53df71daea61d44f8 100755
--- a/hlsl/hlslParseHelper.cpp
+++ b/hlsl/hlslParseHelper.cpp
@@ -147,7 +147,7 @@ bool HlslParseContext::parseShaderStrings(TPpContext& ppContext, TInputScanner&
 //
 bool HlslParseContext::shouldConvertLValue(const TIntermNode* node) const
 {
-    if (node == nullptr)
+    if (node == nullptr || node->getAsTyped() == nullptr)
         return false;
 
     const TIntermAggregate* lhsAsAggregate = node->getAsAggregate();
@@ -157,10 +157,14 @@ bool HlslParseContext::shouldConvertLValue(const TIntermNode* node) const
     if (lhsAsBinary != nullptr &&
         (lhsAsBinary->getOp() == EOpVectorSwizzle || lhsAsBinary->getOp() == EOpIndexDirect))
         lhsAsAggregate = lhsAsBinary->getLeft()->getAsAggregate();
-
     if (lhsAsAggregate != nullptr && lhsAsAggregate->getOp() == EOpImageLoad)
         return true;
 
+    // If it's a syntactic write to a sampler, we will use that to establish
+    // a compile-time alias.
+    if (node->getAsTyped()->getBasicType() == EbtSampler)
+        return true;
+
     return false;
 }
 
@@ -233,7 +237,7 @@ bool HlslParseContext::lValueErrorCheck(const TSourceLoc& loc, const char* op, T
 //
 // Most things are passed through unmodified, except for error checking.
 //
-TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char* op, TIntermTyped* node)
+TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char* op, TIntermTyped*& node)
 {
     if (node == nullptr)
         return nullptr;
@@ -256,6 +260,10 @@ TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char*
 
     // *** If we get here, we're going to apply some conversion to an l-value.
 
+    // Spin off sampler aliasing
+    if (node->getAsTyped()->getBasicType() == EbtSampler)
+        return handleSamplerLvalue(loc, op, node);
+
     // Helper to create a load.
     const auto makeLoad = [&](TIntermSymbol* rhsTmp, TIntermTyped* object, TIntermTyped* coord, const TType& derefType) {
         TIntermAggregate* loadOp = new TIntermAggregate(EOpImageLoad);
@@ -500,6 +508,42 @@ TIntermTyped* HlslParseContext::handleLvalue(const TSourceLoc& loc, const char*
     return node;
 }
 
+// Deal with sampler aliasing: turning assignments into aliases
+TIntermTyped* HlslParseContext::handleSamplerLvalue(const TSourceLoc& loc, const char* op, TIntermTyped*& node)
+{
+    // Can only alias an assignment:  "s1 = s2"
+    TIntermBinary* binary = node->getAsBinaryNode();
+    if (binary == nullptr || node->getAsOperator()->getOp() != EOpAssign ||
+        binary->getLeft() ->getAsSymbolNode() == nullptr ||
+        binary->getRight()->getAsSymbolNode() == nullptr) {
+        error(loc, "can't modify sampler", op, "");
+        return node;
+    }
+
+    // Best is if we are aliasing a flattened struct member "S.s1 = s2",
+    // in which case we want to update the flattening information with the alias,
+    // making everything else work seamlessly.
+    TIntermSymbol* left = binary->getLeft()->getAsSymbolNode();
+    TIntermSymbol* right = binary->getRight()->getAsSymbolNode();
+    for (auto fit = flattenMap.begin(); fit != flattenMap.end(); ++fit) {
+        for (auto mit = fit->second.members.begin(); mit != fit->second.members.end(); ++mit) {
+            if ((*mit)->getUniqueId() == left->getId()) {
+                // found it: update with alias to the existing variable, and don't emit any code
+                (*mit) = new TVariable(&right->getName(), right->getType());
+                (*mit)->setUniqueId(right->getId());
+                // replace node (rest of compiler expects either an error or code to generate)
+                // with pointless access
+                node = binary->getRight();
+                return node;
+            }
+        }
+    }
+
+    warn(loc, "could not create alias for sampler", op, "");
+
+    return node;
+}
+
 void HlslParseContext::handlePragma(const TSourceLoc& loc, const TVector<TString>& tokens)
 {
     if (pragmaCallback)
@@ -1284,7 +1328,7 @@ bool HlslParseContext::wasSplit(const TIntermTyped* node) const
 // Turn an access into an aggregate that was flattened to instead be
 // an access to the individual variable the member was flattened to.
 // Assumes shouldFlatten() or equivalent was called first.
-// Also assumes that initFlattening() and finalizeFlattening() bracket usage.
+// Also assumes that initFlattening() and finalizeFlattening() bracket the usage.
 TIntermTyped* HlslParseContext::flattenAccess(TIntermTyped* base, int member)
 {
     const TType dereferencedType(base->getType(), member);  // dereferenced type
diff --git a/hlsl/hlslParseHelper.h b/hlsl/hlslParseHelper.h
index eeba37e5ee379a6c2450d42d0d782bb59f584c3f..9a98964239c421bbb751cdb495ce3b1304f6b7bc 100755
--- a/hlsl/hlslParseHelper.h
+++ b/hlsl/hlslParseHelper.h
@@ -187,7 +187,8 @@ public:
     virtual void growGlobalUniformBlock(const TSourceLoc&, TType&, const TString& memberName, TTypeList* typeList = nullptr) override;
 
     // Apply L-value conversions.  E.g, turning a write to a RWTexture into an ImageStore.
-    TIntermTyped* handleLvalue(const TSourceLoc&, const char* op, TIntermTyped* node);
+    TIntermTyped* handleLvalue(const TSourceLoc&, const char* op, TIntermTyped*& node);
+    TIntermTyped* handleSamplerLvalue(const TSourceLoc&, const char* op, TIntermTyped*& node);
     bool lValueErrorCheck(const TSourceLoc&, const char* op, TIntermTyped*) override;
 
     TLayoutFormat getLayoutFromTxType(const TSourceLoc&, const TType&);