diff --git a/python/resources/inspectionDescriptions/PyAssignmentToForLoopParameterInspection.html b/python/resources/inspectionDescriptions/PyAssignmentToForLoopParameterInspection.html
new file mode 100644
index 000000000000..5944aee07d3c
--- /dev/null
+++ b/python/resources/inspectionDescriptions/PyAssignmentToForLoopParameterInspection.html
@@ -0,0 +1,13 @@
+
+
+
+ This inspection checks for cases when loop variable is redeclared inside of loop.
+
+
+ for i in xrange(5):
+ for i in xrange(20, 25):
+ print("Inner", i)
+ print("Outer", i)
+
+
+
diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index f11f009b3a5c..424f0f79a19b 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -329,6 +329,7 @@
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 84d4c6d7423b..f780607189a9 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -511,6 +511,9 @@ INSP.none.function.assignment=Function ''{0}'' doesn''t return anything
INSP.NAME.global.undefined=Global variable is undefined at the module level
INSP.NAME.global.$0.undefined=Global variable ''{0}'' is undefined at the module level
+#PyAssignmentToForLoopParameterInspection
+INSP.NAME.assignment.to.for.loop.parameter.display.name=Assignment to 'for' loop parameter
+
# Refactoring
# introduce
refactoring.introduce.name.error=Incorrect name
diff --git a/python/src/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspection.java b/python/src/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspection.java
new file mode 100644
index 000000000000..fcfb364e338f
--- /dev/null
+++ b/python/src/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspection.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.inspections;
+
+import com.intellij.codeInspection.LocalInspectionToolSession;
+import com.intellij.codeInspection.ProblemsHolder;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.psi.PyForPart;
+import com.jetbrains.python.psi.PyTargetExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+//TODO: Try to share logic with AssignmentToForLoopParameterInspection
+
+/**
+ * Checks for cases like
+ *
+ * for i in range(1, 10):
+ * i = "new value"
+ *
+ *
+ * @author link
+ */
+public class PyAssignmentToForLoopParameterInspection extends PyInspection {
+
+ private static final String MESSAGE = PyBundle.message("INSP.NAME.assignment.to.for.loop.parameter.display.name");
+
+ @NotNull
+ @Override
+ public String getDisplayName() {
+ return MESSAGE;
+ }
+
+
+ @NotNull
+ @Override
+ public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
+ boolean isOnTheFly,
+ @NotNull final LocalInspectionToolSession session) {
+ return new Visitor(holder, session);
+ }
+
+ private static class Visitor extends PyInspectionVisitor {
+ private Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
+ super(holder, session);
+ }
+
+ @Override
+ public void visitPyTargetExpression(PyTargetExpression node) {
+ PsiElement variableDeclaration = node.getReference().resolve();
+ if (variableDeclaration == null) {
+ return;
+ }
+ PsiElement variableFirstTimeDeclaration = variableDeclaration.getParent();
+
+ if (variableFirstTimeDeclaration.equals(node.getParent())) {
+ return; //We are checking first time declaration
+ }
+
+ //Find "for" between predecessors until we tree root
+ PsiElement element = variableFirstTimeDeclaration;
+ while (!(element instanceof PsiFile) && element != null) {
+ if (element instanceof PyForPart) {
+ registerProblem(node, MESSAGE);
+ return;
+ }
+ element = element.getParent();
+ }
+ }
+ }
+}
diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/good.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/good.py
new file mode 100644
index 000000000000..3ea1ee1b26c5
--- /dev/null
+++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/good.py
@@ -0,0 +1,9 @@
+for a in (1, 12):
+ for b in (2, 24):
+ for (c, d) in {"C": "D"}.items():
+ (e, f) = (a, d)
+
+i = 12
+print(i)
+(z, x) = (i, 12)
+print(z)
\ No newline at end of file
diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/simpleReassignment.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/simpleReassignment.py
new file mode 100644
index 000000000000..130176454320
--- /dev/null
+++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/simpleReassignment.py
@@ -0,0 +1,3 @@
+for i in range(1, 2):
+ print(i)
+ i = 12
\ No newline at end of file
diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleAssignment.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleAssignment.py
new file mode 100644
index 000000000000..07281c7c9784
--- /dev/null
+++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleAssignment.py
@@ -0,0 +1,3 @@
+for i in [1, 2, 3]:
+ print(i)
+ (i, f) = (1, 2)
\ No newline at end of file
diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleDeclaration.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleDeclaration.py
new file mode 100644
index 000000000000..52905d42e134
--- /dev/null
+++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/tupleDeclaration.py
@@ -0,0 +1,3 @@
+for (k, v) in {"K": "V"}.items():
+ print(k)
+ k = "12"
\ No newline at end of file
diff --git a/python/testData/inspections/PyAssignmentToForLoopParameterInspection/twoLoops.py b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/twoLoops.py
new file mode 100644
index 000000000000..c39f58244512
--- /dev/null
+++ b/python/testData/inspections/PyAssignmentToForLoopParameterInspection/twoLoops.py
@@ -0,0 +1,4 @@
+for i in range(5):
+ for i in range(20, 25):
+ print("Inner", i)
+ print("Outer", i)
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyInspectionTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyInspectionTestCase.java
new file mode 100644
index 000000000000..96fca13931a9
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/fixtures/PyInspectionTestCase.java
@@ -0,0 +1,44 @@
+package com.jetbrains.python.fixtures;
+
+import com.jetbrains.python.inspections.PyInspection;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Helps you to create inspection tests.
+ *
+ * For each case
+ *
+ * - Create file
testData/inspections/_YOUR_INSPECTION_CLASS_SIMPLE_NAME_/CASE_NAME_CAMEL_CASE.py
+ * - Create method
test_YOUR_CASE_NAME_PASCAL_CASE that runs {@link #doTest()}
+ * - Overwrite {@link #isInfo()}, {@link #isWarning()} or {@link #isWeakWarning()} to configure what to check
+ *
+ *
+ * @author link
+ */
+public abstract class PyInspectionTestCase extends PyTestCase {
+
+ @NotNull
+ protected abstract Class extends PyInspection> getInspectionClass();
+
+ /**
+ * Launches test. To be called by test author
+ */
+ protected void doTest() {
+ myFixture.configureByFile("inspections/" + getInspectionClass().getSimpleName() + "/" + getTestName(true) + ".py");
+ myFixture.enableInspections(getInspectionClass());
+ myFixture.checkHighlighting(isWarning(), isInfo(), isWeakWarning());
+ }
+
+
+ protected boolean isWeakWarning() {
+ return true;
+ }
+
+ protected boolean isInfo() {
+ return false;
+ }
+
+ protected boolean isWarning() {
+ return true;
+ }
+}
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspectionTest.java
new file mode 100644
index 000000000000..9231a3d3a55b
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/inspections/PyAssignmentToForLoopParameterInspectionTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.inspections;
+
+import com.jetbrains.python.fixtures.PyInspectionTestCase;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author link
+ */
+public class PyAssignmentToForLoopParameterInspectionTest extends PyInspectionTestCase {
+
+ public void testGood() {
+ doTest();
+ }
+
+ public void testSimpleReassignment() {
+ doTest();
+ }
+
+ public void testTupleAssignment() {
+ doTest();
+ }
+
+ public void testTupleDeclaration() {
+ doTest();
+ }
+
+ public void testTwoLoops() {
+ doTest();
+ }
+
+
+ @NotNull
+ @Override
+ protected Class extends PyInspection> getInspectionClass() {
+ return PyAssignmentToForLoopParameterInspection.class;
+ }
+
+ @Override
+ protected boolean isWeakWarning() {
+ return false;
+ }
+}