Issue #6690: Optimize the bytecode for expressions such as `x in {1, 2, 3}`,
where the right hand operand is a set of constants, by turning the set into
a frozenset and pre-building it as a constant.  The comparison operation
is made against the constant instead of building a new set each time it is
executed (a similar optimization already existed which turned a list of
constants into a pre-built tuple).  Patch and additional tests by Dave
Malcolm.
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 9e8bb69..fcd7ccc 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -1,4 +1,5 @@
 import dis
+import re
 import sys
 from io import StringIO
 import unittest
@@ -115,6 +116,54 @@
                 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
             ],)
 
+    def test_folding_of_lists_of_constants(self):
+        for line, elem in (
+            # in/not in constants with BUILD_LIST should be folded to a tuple:
+            ('a in [1,2,3]', '(1, 2, 3)'),
+            ('a not in ["a","b","c"]', "(('a', 'b', 'c'))"),
+            ('a in [None, 1, None]', '((None, 1, None))'),
+            ('a not in [(1, 2), 3, 4]', '(((1, 2), 3, 4))'),
+            ):
+            asm = dis_single(line)
+            self.assertIn(elem, asm)
+            self.assertNotIn('BUILD_LIST', asm)
+
+    def test_folding_of_sets_of_constants(self):
+        for line, elem in (
+            # in/not in constants with BUILD_SET should be folded to a frozenset:
+            ('a in {1,2,3}', frozenset({1, 2, 3})),
+            ('a not in {"a","b","c"}', frozenset({'a', 'c', 'b'})),
+            ('a in {None, 1, None}', frozenset({1, None})),
+            ('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
+            ('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
+            ):
+            asm = dis_single(line)
+            self.assertNotIn('BUILD_SET', asm)
+
+            # Verify that the frozenset 'elem' is in the disassembly
+            # The ordering of the elements in repr( frozenset ) isn't
+            # guaranteed, so we jump through some hoops to ensure that we have
+            # the frozenset we expect:
+            self.assertIn('frozenset', asm)
+            # Extract the frozenset literal from the disassembly:
+            m = re.match(r'.*(frozenset\({.*}\)).*', asm, re.DOTALL)
+            self.assertTrue(m)
+            self.assertEqual(eval(m.group(1)), elem)
+
+        # Ensure that the resulting code actually works:
+        def f(a):
+            return a in {1, 2, 3}
+
+        def g(a):
+            return a not in {1, 2, 3}
+
+        self.assertTrue(f(3))
+        self.assertTrue(not f(4))
+
+        self.assertTrue(not g(3))
+        self.assertTrue(g(4))
+
+
     def test_folding_of_binops_on_constants(self):
         for line, elem in (
             ('a = 2+3+4', '(9)'),                   # chained fold
diff --git a/Misc/NEWS b/Misc/NEWS
index 4c08298..248f1a7 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,14 @@
 Core and Builtins
 -----------------
 
+- Issue #6690: Optimize the bytecode for expressions such as `x in {1, 2, 3}`,
+  where the right hand operand is a set of constants, by turning the set into
+  a frozenset and pre-building it as a constant.  The comparison operation
+  is made against the constant instead of building a new set each time it is
+  executed (a similar optimization already existed which turned a list of
+  constants into a pre-built tuple).  Patch and additional tests by Dave
+  Malcolm.
+
 - Issue #7622: Improve the split(), rsplit(), splitlines() and replace()
   methods of bytes, bytearray and unicode objects by using a common
   implementation based on stringlib's fast search.  Patch by Florent Xicluna.
diff --git a/Python/peephole.c b/Python/peephole.c
index 104db8c..eeb31d5 100644
--- a/Python/peephole.c
+++ b/Python/peephole.c
@@ -31,7 +31,8 @@
    new constant (c1, c2, ... cn) can be appended.
    Called with codestr pointing to the first LOAD_CONST.
    Bails out with no change if one or more of the LOAD_CONSTs is missing. 
-   Also works for BUILD_LIST when followed by an "in" or "not in" test.
+   Also works for BUILD_LIST and BUILT_SET when followed by an "in" or "not in"
+   test; for BUILD_SET it assembles a frozenset rather than a tuple.
 */
 static int
 tuple_of_constants(unsigned char *codestr, Py_ssize_t n, PyObject *consts)
@@ -41,7 +42,7 @@
 
 	/* Pre-conditions */
 	assert(PyList_CheckExact(consts));
-	assert(codestr[n*3] == BUILD_TUPLE || codestr[n*3] == BUILD_LIST);
+	assert(codestr[n*3] == BUILD_TUPLE || codestr[n*3] == BUILD_LIST || codestr[n*3] == BUILD_SET);
 	assert(GETARG(codestr, (n*3)) == n);
 	for (i=0 ; i<n ; i++)
 		assert(codestr[i*3] == LOAD_CONST);
@@ -59,6 +60,16 @@
 		PyTuple_SET_ITEM(newconst, i, constant);
 	}
 
+	/* If it's a BUILD_SET, use the PyTuple we just built to create a
+	  PyFrozenSet, and use that as the constant instead: */
+	if (codestr[n*3] == BUILD_SET) {
+		PyObject *tuple = newconst;
+		newconst = PyFrozenSet_New(tuple);
+		Py_DECREF(tuple);
+		if (newconst == NULL)
+			return 0;
+	}
+
 	/* Append folded constant onto consts */
 	if (PyList_Append(consts, newconst)) {
 		Py_DECREF(newconst);
@@ -436,20 +447,21 @@
 				cumlc = 0;
 				break;
 
-				/* Try to fold tuples of constants (includes a case for lists
+				/* Try to fold tuples of constants (includes a case for lists and sets
 				   which are only used for "in" and "not in" tests).
 				   Skip over BUILD_SEQN 1 UNPACK_SEQN 1.
 				   Replace BUILD_SEQN 2 UNPACK_SEQN 2 with ROT2.
 				   Replace BUILD_SEQN 3 UNPACK_SEQN 3 with ROT3 ROT2. */
 			case BUILD_TUPLE:
 			case BUILD_LIST:
+			case BUILD_SET:
 				j = GETARG(codestr, i);
 				h = i - 3 * j;
 				if (h >= 0  &&
 				    j <= lastlc	 &&
 				    ((opcode == BUILD_TUPLE && 
 				      ISBASICBLOCK(blocks, h, 3*(j+1))) ||
-				     (opcode == BUILD_LIST && 
+				     ((opcode == BUILD_LIST || opcode == BUILD_SET) && 
 				      codestr[i+3]==COMPARE_OP && 
 				      ISBASICBLOCK(blocks, h, 3*(j+2)) &&
 				      (GETARG(codestr,i+3)==6 ||