Resolved Bug 334457 - [compiler][null] check compatibility of inherited null contracts
diff --git a/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/CompilerAdaptation.java b/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/CompilerAdaptation.java
index 18c076f..78fd279 100644
--- a/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/CompilerAdaptation.java
+++ b/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/CompilerAdaptation.java
@@ -46,6 +46,7 @@
 import org.eclipse.jdt.internal.compiler.flow.InsideSubRoutineFlowContext;
 import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo;
 import org.eclipse.jdt.internal.compiler.impl.IrritantSet;
+import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
 import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
 import org.eclipse.jdt.internal.compiler.lookup.Binding;
 import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
@@ -598,6 +599,11 @@
 		<- after
 		void checkAgainstInheritedMethods(MethodBinding currentMethod, MethodBinding[] methods, int length, MethodBinding[] allInheritedMethods);
 
+		void checkNullContractInheritance(MethodBinding currentMethod, MethodBinding[] methods, int length)
+		<- after
+		void checkConcreteInheritedMethod(MethodBinding concreteMethod, MethodBinding[] abstractMethods)
+			with { currentMethod <- concreteMethod, methods <- abstractMethods, length <- abstractMethods.length }
+
 		void checkNullContractInheritance(MethodBinding currentMethod, MethodBinding[] methods, int length) {
 			// TODO: change traversal: process all methods at once!
 			for (int i = length; --i >= 0;)
@@ -609,23 +615,31 @@
 			long inheritedBits = inheritedMethod.getTagBits();
 			long currentBits = currentMethod.getTagBits();
 			LookupEnvironment environment = this.getEnvironment();
+			AbstractMethodDeclaration srcMethod = null;
+			if (getType().isSame(currentMethod.getDeclaringClass())) // is currentMethod from the current type?
+				srcMethod = currentMethod.sourceMethod();
 
 			// return type:
 			if ((inheritedBits & TagBits.AnnotationNonNull) != 0) {
 				long currentNullBits = currentBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable);
-				if (currentNullBits != TagBits.AnnotationNonNull) {				
-					AbstractMethodDeclaration methodDecl = currentMethod.sourceMethod();
-					getType().problemReporter().illegalReturnRedefinition(methodDecl, inheritedMethod,
-																environment.getNonNullAnnotationName());
+				if (currentNullBits != TagBits.AnnotationNonNull) {
+					if (srcMethod != null) {
+						getType().problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod,
+																	environment.getNonNullAnnotationName());
+					} else {
+						getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
+						return;
+					}
 				}
 			}
 
 			// parameters:
-			Argument[] currentArguments = currentMethod.sourceMethod().getArguments();
+			Argument[] currentArguments = srcMethod == null ? null : srcMethod.getArguments();
 			if (inheritedMethod.parameterNonNullness != null) {
 				// inherited method has null-annotations, check compatibility:
 
 				for (int i = 0; i < inheritedMethod.parameterNonNullness.length; i++) {
+					Argument currentArgument = currentArguments == null ? null : currentArguments[i];
 					
 					Boolean inheritedNonNullNess = inheritedMethod.parameterNonNullness[i];
 					Boolean currentNonNullNess = (currentMethod.parameterNonNullness == null)
@@ -640,23 +654,30 @@
 							} else {
 								annotationName = environment.getNullableAnnotationName();
 							}
-								
-							getType().problemReporter().parameterLackingNonNullAnnotation(
-									currentArguments[i],
-									inheritedMethod.getDeclaringClass(),
-									needNonNull,
-									annotationName);
-							continue;
+							if (currentArgument != null) {
+								getType().problemReporter().parameterLackingNonNullAnnotation(
+										currentArgument,
+										inheritedMethod.getDeclaringClass(),
+										needNonNull,
+										annotationName);
+								continue;
+							} else {
+								getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
+								break;
+							}
 						}						
 					}
 					if (inheritedNonNullNess != Boolean.TRUE) {		// super parameter is not restricted to @NonNull
 						if (currentNonNullNess == Boolean.TRUE) { 	// current parameter is restricted to @NonNull
-							getType().problemReporter().illegalRedefinitionToNonNullParameter(
-																currentArguments[i],
+							if (currentArgument != null)
+								getType().problemReporter().illegalRedefinitionToNonNullParameter(
+																currentArgument,
 																inheritedMethod.getDeclaringClass(),
 																inheritedNonNullNess == null
 																? null
 																: environment.getNullableAnnotationName());
+							else
+								getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
 						} 
 					}
 				}
@@ -664,10 +685,15 @@
 				// super method has no annotations but current has
 				for (int i = 0; i < currentMethod.parameterNonNullness.length; i++) {
 					if (currentMethod.parameterNonNullness[i] == Boolean.TRUE) { // tightening from unconstrained to @NonNull
-						getType().problemReporter().illegalRedefinitionToNonNullParameter(
-																		currentArguments[i],
-																		inheritedMethod.getDeclaringClass(),
-																		null);
+						if (currentArguments != null) {
+							getType().problemReporter().illegalRedefinitionToNonNullParameter(
+																			currentArguments[i],
+																			inheritedMethod.getDeclaringClass(),
+																			null);
+						} else {
+							getType().problemReporter().cannotImplementIncompatibleNullness(currentMethod, inheritedMethod);
+							break;
+						}
 					}
 				}
 			}
@@ -730,6 +756,8 @@
 		
 		long computeTypeAnnotationTagBits()	-> long getAnnotationTagBits();
 		
+		boolean isSame(Object other) 		-> boolean equals(Object other);
+		
 		private TypeBinding nullnessDefaultAnnotation;
 		private int nullnessDefaultInitialized = 0; // 0: nothing; 1: type; 2: package
 
@@ -1211,6 +1239,8 @@
 	 */
 	protected class ProblemReporter playedBy ProblemReporter {
 
+		ReferenceContext getReferenceContext() -> get ReferenceContext referenceContext;
+		
 		@SuppressWarnings("decapsulation")
 		void handle(int problemId, String[] problemArguments, String[] messageArguments, int severity,
 				int problemStartPosition, int problemEndPosition)
@@ -1394,6 +1424,31 @@
 			String[] args = { new String(CharOperation.concatWith(nullAnnotationName, '.')) };
 			this.handle(IProblem.MissingNullAnnotationType, args, args, 0, 0);	
 		}
+
+		public void cannotImplementIncompatibleNullness(MethodBinding currentMethod, MethodBinding inheritedMethod) {
+			ReferenceContext ctx = getReferenceContext();
+			int sourceStart = 0, sourceEnd = 0;
+			if (ctx instanceof TypeDeclaration) {
+				sourceStart = ((TypeDeclaration) ctx).sourceStart;
+				sourceEnd =   ((TypeDeclaration) ctx).sourceEnd;
+			}
+			String[] problemArguments = {
+					new String(currentMethod.readableName()),
+					new String(currentMethod.getDeclaringClass().readableName()),
+					new String(inheritedMethod.getDeclaringClass().readableName())
+				};
+			String[] messageArguments = {
+					new String(currentMethod.shortReadableName()),
+					new String(currentMethod.getDeclaringClass().shortReadableName()),
+					new String(inheritedMethod.getDeclaringClass().shortReadableName())
+				};
+			this.handle(
+					IProblem.CannotImplementIncompatibleNullness,
+					problemArguments,
+					messageArguments,
+					sourceStart,
+					sourceEnd);
+		}
 		// NOTE: adaptation of toString() omitted
 	}
 	
diff --git a/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/Constants.java b/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/Constants.java
index 8487411..4f464f2 100644
--- a/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/Constants.java
+++ b/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/Constants.java
@@ -70,6 +70,8 @@
 		int PotentialNullMessageSendReference = Internal + 919;
 		/** @since 3.7 */
 		int RedundantNullCheckOnNonNullMessageSend = Internal + 920;
+		/** @since 3.7 */
+		int CannotImplementIncompatibleNullness = Internal + 921;
 	}
 	
 	/** Translate from a nullness annotation to the corresponding tag bit or 0L. */
diff --git a/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/problem_messages.properties b/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/problem_messages.properties
index 4871e6c..a4b0918 100644
--- a/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/problem_messages.properties
+++ b/contrib/org.eclipse.objectteams.jdt.nullity/src/org/eclipse/objectteams/internal/jdt/nullity/problem_messages.properties
@@ -22,3 +22,4 @@
 918 = Missing null annotation: inherited method from {1} declares this parameter as @{2}
 919 = Potential null pointer access: The method {0} may return null
 920 = Redundant null check: The method {0} cannot return null
+921 = The method {0} from class {1} cannot implement the corresponding method from type {2} due to incompatible nullness constraints. 
\ No newline at end of file