Repository: nvaccess/nvda

Issue:
Python 3.8 (porting): "variable is value" aka identity comparison with a literal raises syntax error if warn on error option is specified
Although syntax warning is given, if Python is run with -Werror (convert warnings to errors) switch, the following raises syntax error exception:

### Steps to reproduce:
1. Prepare NVDA from source with `Python 3.7`.
2. If `Python 3.8 32-bit` is installed, install `Python 3.8` versions of `wxPython` (latest `4.1.0 snapshot` is required).
3. Run NVDA from source with errors turned on (`py -3.8 -Werror path/to/nvda/source/nvda.pyw`).

### Actual behavior:
If statement of the form:
`variable is var:`
is encountered, Python 3.8 raises syntax error.

### Expected behavior:
NO errors.

### System configuration
#### NVDA installed/portable/running from source:
Source

#### NVDA version:
master.2d73010

#### Windows version:
Windows 10 Version 2004 (build 19041.172)

#### Name and version of other software in use when reproducing the issue:
Python 3.7.7, 3.8.2 (both 32-bit)

#### Other information about your system:
Being used to test Python 3.8 compatibility

### Other questions
#### Does the issue still occur after restarting your computer?
Yes

#### Have you tried any other versions of NVDA? If so, please report their behaviors.
NO errors in Python 3.7 version of NVDA (including latest alpha snapshot)

#### If addons are disabled, is your problem still occuring?
Yes

#### Did you try to run the COM registry fixing tool in NVDA menu / tools?
Does not apply

### Motivation for running with errors turned on:
Although syntax warning would suffice in that errors will not be logged, running with errors turned on ensures testing for Python syntax errors that are easily ignored.

### Solution:

Convert instances of "variable is value" statement to "variable {condition} value" statement such as:

* `someStr is value` -> `someStr == value`
* `someNum is not something` -> `someNum != something`

This issue was observed with:

* `buildVersion`: constructing NVDA version string
* `Browse mode`: checking for headers


Discussion:
One approach to doing this is to use a custom handler for 2to3. This makes these kinds of conversions much more thorough than a regex based approach because it understands python grammar.

You could check out a fixer I wrote to help [investigate string literals in NVDA](https://github.com/nvaccess/2to3/commit/8c49d86525b63b202cfeb2e6fce8ec2a809d3c39)

In particular the following article was helpful: 
http://python3porting.com/fixers.html#find-pattern

I'd strongly recommend writing a couple of unit tests for your fixer to start with. It is then much easier to work on it iteratively. 
Hi,

Investigating both a fixer and regexp route.

For others: context surrounding this issue is explained more thoroughly here:

https://adamj.eu/tech/2020/01/21/why-does-python-3-8-syntaxwarning-for-is-literal/

Thanks.
Hi,

Actually, there is an easier route: checking for Flake8 error F632. But since this check will take minutes (checking parts of source code), I'll take this route if necessary.

Thanks.

Resolving patch:
diff --git a/source/browseMode.py b/source/browseMode.py
--- a/source/browseMode.py
+++ b/source/browseMode.py
@@ -226,12 +226,12 @@ def _getLabelForProperties(self, labelPropertyGetter):
 			or "lambda property: getattr(self.obj, property, None)" for an L{NVDAObject}.
 		"""
 		content = self.textInfo.text.strip()
-		if self.itemType is "heading":
+		if self.itemType == "heading":
 			# Output: displayed text of the heading.
 			return content
 		labelParts = None
 		name = labelPropertyGetter("name")
-		if self.itemType is "landmark":
+		if self.itemType == "landmark":
 			landmark = aria.landmarkRoles.get(labelPropertyGetter("landmark"))
 			# Example output: main menu; navigation
 			labelParts = (name, landmark)
@@ -242,7 +242,7 @@ def _getLabelForProperties(self, labelPropertyGetter):
 			unlabeled = _("Unlabeled")
 			realStates = labelPropertyGetter("states")
 			labeledStates = " ".join(controlTypes.processAndLabelStates(role, realStates, controlTypes.REASON_FOCUS))
-			if self.itemType is "formField":
+			if self.itemType == "formField":
 				if role in (controlTypes.ROLE_BUTTON,controlTypes.ROLE_DROPDOWNBUTTON,controlTypes.ROLE_TOGGLEBUTTON,controlTypes.ROLE_SPLITBUTTON,controlTypes.ROLE_MENUBUTTON,controlTypes.ROLE_DROPDOWNBUTTONGRID,controlTypes.ROLE_SPINBUTTON,controlTypes.ROLE_TREEVIEWBUTTON):
 					# Example output: Mute; toggle button; pressed
 					labelParts = (content or name or unlabeled, roleText, labeledStates)
diff --git a/source/buildVersion.py b/source/buildVersion.py
--- a/source/buildVersion.py
+++ b/source/buildVersion.py
@@ -58,7 +58,7 @@ def formatVersionForGUI(year, major, minor):
 		raise ValueError(
 			"Three values must be provided. Got year={}, major={}, minor={}".format(year, major, minor)
 		)
-	if minor is 0:
+	if minor == 0:
 		return "{y}.{M}".format(y=year, M=major)
 	return "{y}.{M}.{m}".format(y=year, M=major, m=minor)
 
diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py
--- a/source/virtualBuffers/gecko_ia2.py
+++ b/source/virtualBuffers/gecko_ia2.py
@@ -114,7 +114,7 @@ def _normalizeControlField(self,attrs):
 			states.discard(controlTypes.STATE_PRESSED)
 		attrs['role']=role
 		attrs['states']=states
-		if level is not "" and level is not None:
+		if level != "" and level is not None:
 			attrs['level']=level
 		if landmark:
 			attrs["landmark"]=landmark


Test patch: