/*
* Copyright 2017 The Closure Compiler Authors.
*
* 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.google.javascript.jscomp;
import com.google.javascript.rhino.FunctionTypeI;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.ObjectTypeI;
import com.google.javascript.rhino.TypeI;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Signals that the first type and the second type have been
* used interchangeably.
*
* Type-based optimizations should take this into account
* so that they don't wreck code with type warnings.
*/
class TypeMismatch {
final TypeI typeA;
final TypeI typeB;
final JSError src;
/**
* It's the responsibility of the class that creates the
* {@code TypeMismatch} to ensure that {@code a} and {@code b} are
* non-matching types.
*/
TypeMismatch(TypeI a, TypeI b, JSError src) {
this.typeA = a;
this.typeB = b;
this.src = src;
}
static void registerIfMismatch(
List<TypeMismatch> mismatches, List<TypeMismatch> implicitInterfaceUses,
TypeI found, TypeI required, JSError error) {
if (found != null && required != null && !found.isSubtypeWithoutStructuralTyping(required)) {
registerMismatch(mismatches, implicitInterfaceUses, found, required, error);
}
}
static void registerMismatch(
List<TypeMismatch> mismatches, List<TypeMismatch> implicitInterfaceUses,
TypeI found, TypeI required, JSError error) {
// Don't register a mismatch for differences in null or undefined or if the
// code didn't downcast.
found = removeNullUndefinedAndTemplates(found);
required = removeNullUndefinedAndTemplates(required);
if (found.isSubtypeOf(required) || required.isSubtypeOf(found)) {
boolean strictMismatch =
!found.isSubtypeWithoutStructuralTyping(required)
&& !required.isSubtypeWithoutStructuralTyping(found);
if (strictMismatch) {
implicitInterfaceUses.add(new TypeMismatch(found, required, error));
}
return;
}
mismatches.add(new TypeMismatch(found, required, error));
if (found.isFunctionType() && required.isFunctionType()) {
FunctionTypeI fnTypeA = found.toMaybeFunctionType();
FunctionTypeI fnTypeB = required.toMaybeFunctionType();
Iterator<TypeI> paramItA = fnTypeA.getParameterTypes().iterator();
Iterator<TypeI> paramItB = fnTypeB.getParameterTypes().iterator();
while (paramItA.hasNext() && paramItB.hasNext()) {
TypeMismatch.registerIfMismatch(
mismatches, implicitInterfaceUses, paramItA.next(), paramItB.next(), error);
}
TypeMismatch.registerIfMismatch(
mismatches, implicitInterfaceUses,
fnTypeA.getReturnType(), fnTypeB.getReturnType(), error);
}
}
static void recordImplicitUseOfNativeObject(
List<TypeMismatch> mismatches, Node src, TypeI sourceType, TypeI targetType) {
sourceType = sourceType.restrictByNotNullOrUndefined();
targetType = targetType.restrictByNotNullOrUndefined();
if (sourceType.isInstanceofObject()
&& !targetType.isInstanceofObject() && !targetType.isUnknownType()) {
// We don't report a type error, but we still need to construct a JSError,
// for people who enable the invalidation diagnostics in DisambiguateProperties.
String msg = "Implicit use of Object type: " + sourceType + " as type: " + targetType;
JSError err = JSError.make(src, TypeValidator.TYPE_MISMATCH_WARNING, msg);
mismatches.add(new TypeMismatch(sourceType, targetType, err));
}
}
static void recordImplicitInterfaceUses(
List<TypeMismatch> implicitInterfaceUses, Node src, TypeI sourceType, TypeI targetType) {
sourceType = removeNullUndefinedAndTemplates(sourceType);
targetType = removeNullUndefinedAndTemplates(targetType);
if (targetType.isUnknownType()) {
return;
}
boolean strictMismatch =
!sourceType.isSubtypeWithoutStructuralTyping(targetType)
&& !targetType.isSubtypeWithoutStructuralTyping(sourceType);
boolean mismatch = !sourceType.isSubtypeOf(targetType) && !targetType.isSubtypeOf(sourceType);
if (strictMismatch || mismatch) {
// We don't report a type error, but we still need to construct a JSError,
// for people who enable the invalidation diagnostics in DisambiguateProperties.
// Use the empty string as the error string. Creating an actual error message can be slow
// for large types; we create an error string lazily in DisambiguateProperties.
JSError err = JSError.make(src, TypeValidator.TYPE_MISMATCH_WARNING, "");
implicitInterfaceUses.add(new TypeMismatch(sourceType, targetType, err));
}
}
private static TypeI removeNullUndefinedAndTemplates(TypeI t) {
TypeI result = t.restrictByNotNullOrUndefined();
ObjectTypeI obj = result.toMaybeObjectType();
if (obj != null && obj.isGenericObjectType()) {
return obj.instantiateGenericsWithUnknown();
}
return result;
}
@Override public boolean equals(Object object) {
if (object instanceof TypeMismatch) {
TypeMismatch that = (TypeMismatch) object;
return (that.typeA.equals(this.typeA) && that.typeB.equals(this.typeB))
|| (that.typeB.equals(this.typeA) && that.typeA.equals(this.typeB));
}
return false;
}
@Override public int hashCode() {
return Objects.hash(typeA, typeB);
}
@Override public String toString() {
return "(" + typeA + ", " + typeB + ")";
}
}