Skip to content

Commit

Permalink
[NTI] Finish support for abstract classes and methods.
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=139624919
  • Loading branch information
dimvar authored and blickly committed Nov 19, 2016
1 parent 7ef9044 commit 1ab5aca
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 29 deletions.
2 changes: 2 additions & 0 deletions src/com/google/javascript/jscomp/DiagnosticGroups.java
Expand Up @@ -313,6 +313,7 @@ public DiagnosticGroup forName(String name) {
JSTypeCreatorFromJSDoc.IMPLEMENTS_WITHOUT_CONSTRUCTOR,
JSTypeCreatorFromJSDoc.INHERITANCE_CYCLE,
JSTypeCreatorFromJSDoc.UNION_IS_UNINHABITABLE,
GlobalTypeInfo.ABSTRACT_METHOD_IN_CONCRETE_CLASS,
GlobalTypeInfo.ANONYMOUS_NOMINAL_TYPE,
GlobalTypeInfo.CANNOT_INIT_TYPEDEF,
GlobalTypeInfo.CANNOT_OVERRIDE_FINAL_METHOD,
Expand All @@ -336,6 +337,7 @@ public DiagnosticGroup forName(String name) {
GlobalTypeInfo.SUPER_INTERFACES_HAVE_INCOMPATIBLE_PROPERTIES,
GlobalTypeInfo.UNKNOWN_OVERRIDE,
GlobalTypeInfo.UNRECOGNIZED_TYPE_NAME,
NewTypeInference.ABSTRACT_METHOD_NOT_CALLABLE,
NewTypeInference.ASSERT_FALSE,
NewTypeInference.CANNOT_BIND_CTOR,
NewTypeInference.CONST_REASSIGNED,
Expand Down
40 changes: 38 additions & 2 deletions src/com/google/javascript/jscomp/GlobalTypeInfo.java
Expand Up @@ -224,7 +224,24 @@ class GlobalTypeInfo implements CompilerPass, TypeIRegistry {
"JSC_NTI_INTERFACE_METHOD_NOT_EMPTY",
"interface member functions must have an empty body");

static final DiagnosticType ABSTRACT_METHOD_IN_CONCRETE_CLASS =
DiagnosticType.warning(
"JSC_NTI_ABSTRACT_METHOD_IN_CONCRETE_CLASS",
"Abstract methods can only appear in abstract classes. "
+ "Please declare class {0} as @abstract");

static final DiagnosticType ABSTRACT_METHOD_IN_INTERFACE =
DiagnosticType.warning(
"JSC_NTI_ABSTRACT_METHOD_IN_INTERFACE",
"Abstract methods cannot appear in interfaces");

static final DiagnosticType ABSTRACT_METHOD_NOT_IMPLEMENTED_IN_CONCRETE_CLASS =
DiagnosticType.warning(
"JSC_NTI_ABSTRACT_METHOD_NOT_IMPLEMENTED_IN_CONCRETE_CLASS",
"Abstract method {0} from superclass {1} not implemented");

static final DiagnosticGroup COMPATIBLE_DIAGNOSTICS = new DiagnosticGroup(
ABSTRACT_METHOD_IN_CONCRETE_CLASS,
CANNOT_OVERRIDE_FINAL_METHOD,
DICT_WITHOUT_CTOR,
DUPLICATE_PROP_IN_ENUM,
Expand All @@ -246,6 +263,8 @@ class GlobalTypeInfo implements CompilerPass, TypeIRegistry {
WRONG_PARAMETER_COUNT);

static final DiagnosticGroup NEW_DIAGNOSTICS = new DiagnosticGroup(
ABSTRACT_METHOD_IN_INTERFACE,
ABSTRACT_METHOD_NOT_IMPLEMENTED_IN_CONCRETE_CLASS,
ANONYMOUS_NOMINAL_TYPE,
CANNOT_ADD_PROPERTIES_TO_TYPEDEF,
CANNOT_INIT_TYPEDEF,
Expand Down Expand Up @@ -546,6 +565,14 @@ private void checkAndFinalizeNominalType(RawNominalType rawType) {
Preconditions.checkState(superClass.isFinalized());
// TODO(blickly): Can we optimize this to skip unnecessary iterations?
for (String pname : superClass.getAllPropsOfClass()) {
if (superClass.isAbstractClass()
&& superClass.hasAbstractMethod(pname)
&& !rawType.isAbstractClass()
&& !rawType.mayHaveOwnProp(pname)) {
warnings.add(JSError.make(
rawType.getDefSite(), ABSTRACT_METHOD_NOT_IMPLEMENTED_IN_CONCRETE_CLASS,
pname, superClass.getName()));
}
nonInheritedPropNames.remove(pname);
checkSuperProperty(rawType, superClass, pname,
propMethodTypesToProcess, propTypesToProcess);
Expand Down Expand Up @@ -1128,7 +1155,7 @@ private void maybeRecordNominalType(
? ObjectKind.STRUCT : (fnDoc.makesDicts() ? ObjectKind.DICT : ObjectKind.UNRESTRICTED);
if (fnDoc.isConstructor()) {
rawType = RawNominalType.makeClass(
commonTypes, defSite, qname, typeParameters, objKind);
commonTypes, defSite, qname, typeParameters, objKind, fnDoc.isAbstract());
} else if (fnDoc.usesImplicitMatch()) {
rawType = RawNominalType.makeStructuralInterface(
commonTypes, defSite, qname, typeParameters, objKind);
Expand Down Expand Up @@ -1175,7 +1202,7 @@ private void maybeRecordBuiltinType(
// Create a separate raw type for object literals
RawNominalType objLitRawType = RawNominalType.makeClass(
commonTypes, rawType.getDefSite(), JSTypes.OBJLIT_CLASS_NAME,
ImmutableList.<String>of(), ObjectKind.UNRESTRICTED);
ImmutableList.<String>of(), ObjectKind.UNRESTRICTED, false);
objLitRawType.addSuperClass(rawType.getAsNominalType());
commonTypes.setLiteralObjNominalType(objLitRawType);
break;
Expand Down Expand Up @@ -2410,6 +2437,15 @@ private void mayAddPropToPrototype(
}
propertyDefs.put(rawType, pname, new PropertyDef(defSite, methodType, methodScope));

// Warn for abstract methods not in abstract classes
if (methodType != null && methodType.isAbstract() && !rawType.isAbstractClass()) {
if (rawType.isClass()) {
warnings.add(JSError.make(defSite, ABSTRACT_METHOD_IN_CONCRETE_CLASS, rawType.getName()));
} else if (rawType.isInterface()) {
warnings.add(JSError.make(defSite, ABSTRACT_METHOD_IN_INTERFACE));
}
}

// Add the property to the class with the appropriate type.
boolean isConst = isConst(defSite);
if (propDeclType != null || isConst) {
Expand Down
33 changes: 22 additions & 11 deletions src/com/google/javascript/jscomp/NewTypeInference.java
Expand Up @@ -170,9 +170,9 @@ final class NewTypeInference implements CompilerPass {
"JSC_NTI_NOT_A_CONSTRUCTOR",
"Expected a constructor but found type {0}.");

static final DiagnosticType INSTANTIATE_ABSTRACT_CLASS =
static final DiagnosticType CANNOT_INSTANTIATE_ABSTRACT_CLASS =
DiagnosticType.warning(
"JSC_NTI_INSTANTIATE_ABSTRACT_CLASS",
"JSC_NTI_CANNOT_INSTANTIATE_ABSTRACT_CLASS",
"Cannot instantiate abstract class {0}.");

static final DiagnosticType UNDEFINED_SUPER_CLASS =
Expand Down Expand Up @@ -292,6 +292,11 @@ final class NewTypeInference implements CompilerPass {
+ "left : {0}\n"
+ "right: {1}");

static final DiagnosticType ABSTRACT_METHOD_NOT_CALLABLE =
DiagnosticType.warning(
"JSC_NTI_ABSTRACT_METHOD_NOT_CALLABLE",
"Abstract method {0} cannot be called");

// Not part of ALL_DIAGNOSTICS because it should not be enabled with
// --jscomp_error=newCheckTypes. It should only be enabled explicitly.

Expand All @@ -306,8 +311,10 @@ final class NewTypeInference implements CompilerPass {
"This {0} expression has the unknown type.");

static final DiagnosticGroup COMPATIBLE_DIAGNOSTICS = new DiagnosticGroup(
ABSTRACT_METHOD_NOT_CALLABLE,
ASSERT_FALSE,
CANNOT_BIND_CTOR,
CANNOT_INSTANTIATE_ABSTRACT_CLASS,
CONST_PROPERTY_DELETED,
CONST_PROPERTY_REASSIGNED,
CONST_REASSIGNED,
Expand All @@ -321,7 +328,6 @@ final class NewTypeInference implements CompilerPass {
ILLEGAL_PROPERTY_CREATION,
IN_USED_WITH_STRUCT,
INEXISTENT_PROPERTY,
INSTANTIATE_ABSTRACT_CLASS,
INVALID_ARGUMENT_TYPE,
INVALID_CAST,
INVALID_INDEX_TYPE,
Expand Down Expand Up @@ -1928,8 +1934,8 @@ private EnvTypePair analyzeCallNewFwd(
if (!funType.isSomeConstructorOrInterface() || funType.isInterfaceDefinition()) {
warnings.add(JSError.make(expr, NOT_A_CONSTRUCTOR, funType.toString()));
return analyzeCallNodeArgsFwdWhenError(expr, envAfterCallee);
} else if (funType.isAbstract()) {
warnings.add(JSError.make(expr, INSTANTIATE_ABSTRACT_CLASS, funType.toString()));
} else if (funType.isConstructorOfAbstractClass()) {
warnings.add(JSError.make(expr, CANNOT_INSTANTIATE_ABSTRACT_CLASS, funType.toString()));
return analyzeCallNodeArgsFwdWhenError(expr, envAfterCallee);
}
}
Expand Down Expand Up @@ -3107,13 +3113,18 @@ private EnvTypePair analyzePropAccessFwd(Node receiver, String pname,
return new EnvTypePair(pair.env, requiredType);
}
FunctionType ft = recvType.getFunTypeIfSingletonObj();
if (ft != null && pname.equals("call")) {
return new EnvTypePair(pair.env,
commonTypes.fromFunctionType(ft.transformByCallProperty()));
}
if (ft != null && pname.equals("apply")) {
if (ft != null && (pname.equals("call") || pname.equals("apply"))) {
if (ft.isAbstract()) {
// We don't check if the parent of the property access is a call node.
// This catches calls that are a few nodes away, and also warns on .call/.apply
// accesses that do not result in calls (these should be very rare).
String funName = receiver.isQualifiedName() ? receiver.getQualifiedName() : "";
warnings.add(JSError.make(propAccessNode, ABSTRACT_METHOD_NOT_CALLABLE, funName));
}
return new EnvTypePair(pair.env,
commonTypes.fromFunctionType(ft.transformByApplyProperty()));
pname.equals("call")
? commonTypes.fromFunctionType(ft.transformByCallProperty())
: commonTypes.fromFunctionType(ft.transformByApplyProperty()));
}
if (this.convention.isSuperClassReference(pname)) {
if (ft != null && ft.isUniqueConstructor()) {
Expand Down
5 changes: 5 additions & 0 deletions src/com/google/javascript/jscomp/newtypes/FunctionType.java
Expand Up @@ -142,6 +142,11 @@ public boolean isAbstract() {
return isAbstract;
}

public boolean isConstructorOfAbstractClass() {
return isUniqueConstructor()
&& this.nominalType.getNominalTypeIfSingletonObj().isAbstractClass();
}

static FunctionType normalized(
JSTypes commonTypes,
List<JSType> requiredFormals,
Expand Down
Expand Up @@ -856,7 +856,10 @@ private DeclaredFunctionType getFunTypeFromTypicalFunctionJsdoc(
getThisOrNewType(thisRoot.getFirstChild(), registry, typeParameters));
}

builder.addAbstract(jsdoc.isAbstract());
if (!jsdoc.isConstructor()) {
builder.addAbstract(jsdoc.isAbstract());
}

return builder.buildDeclaration();
}

Expand Down
8 changes: 8 additions & 0 deletions src/com/google/javascript/jscomp/newtypes/NominalType.java
Expand Up @@ -248,6 +248,10 @@ public boolean isClass() {
return this.rawType.isClass();
}

public boolean isAbstractClass() {
return this.rawType.isAbstractClass();
}

public boolean isInterface() {
return this.rawType.isInterface();
}
Expand Down Expand Up @@ -355,6 +359,10 @@ boolean mayHaveProp(String pname) {
return this.rawType.mayHaveProp(pname);
}

public boolean hasAbstractMethod(String pname) {
return this.rawType.hasAbstractMethod(pname);
}

boolean isSubtypeOf(NominalType other, SubtypeCache subSuperMap) {
return isNominalSubtypeOf(other)
|| other.isStructuralInterface() && isStructuralSubtypeOf(other, subSuperMap);
Expand Down
26 changes: 19 additions & 7 deletions src/com/google/javascript/jscomp/newtypes/RawNominalType.java
Expand Up @@ -60,6 +60,7 @@ public final class RawNominalType extends Namespace {
private final Set<RawNominalType> subtypes = new LinkedHashSet<>();
private ImmutableSet<NominalType> interfaces = null;
private final Kind kind;
private final boolean isAbstractClass;
// Used in GlobalTypeInfo to find type mismatches in the inheritance chain.
private ImmutableSet<String> allProps = null;
// In GlobalTypeInfo, we request (wrapped) RawNominalTypes in various
Expand All @@ -80,8 +81,8 @@ private enum Kind {
}

private RawNominalType(
JSTypes commonTypes, Node defSite,
String name, ImmutableList<String> typeParameters, Kind kind, ObjectKind objectKind) {
JSTypes commonTypes, Node defSite, String name,
ImmutableList<String> typeParameters, Kind kind, ObjectKind objectKind, boolean isAbstract) {
super(commonTypes, name, defSite);
Preconditions.checkNotNull(objectKind);
Preconditions.checkState(isValidDefsite(defSite), "Invalid defsite %s", defSite);
Expand All @@ -96,6 +97,7 @@ private RawNominalType(
this.kind = isBuiltinHelper(name, "IObject", defSite) ? Kind.RECORD : kind;
this.objectKind = isBuiltinHelper(name, "IObject", defSite)
? ObjectKind.UNRESTRICTED : objectKind;
this.isAbstractClass = isAbstract;
this.wrappedAsNominal = new NominalType(ImmutableMap.<String, JSType>of(), this);
ObjectType objInstance;

Expand Down Expand Up @@ -128,10 +130,10 @@ private static boolean isValidDefsite(Node defSite) {
return false;
}

public static RawNominalType makeClass(JSTypes commonTypes,
Node defSite, String name, ImmutableList<String> typeParameters, ObjectKind objKind) {
public static RawNominalType makeClass(JSTypes commonTypes, Node defSite, String name,
ImmutableList<String> typeParameters, ObjectKind objKind, boolean isAbstract) {
return new RawNominalType(
commonTypes, defSite, name, typeParameters, Kind.CLASS, objKind);
commonTypes, defSite, name, typeParameters, Kind.CLASS, objKind, isAbstract);
}

public static RawNominalType makeNominalInterface(JSTypes commonTypes,
Expand All @@ -140,7 +142,7 @@ public static RawNominalType makeNominalInterface(JSTypes commonTypes,
objKind = ObjectKind.UNRESTRICTED;
}
return new RawNominalType(
commonTypes, defSite, name, typeParameters, Kind.INTERFACE, objKind);
commonTypes, defSite, name, typeParameters, Kind.INTERFACE, objKind, false);
}

public static RawNominalType makeStructuralInterface(JSTypes commonTypes,
Expand All @@ -149,7 +151,7 @@ public static RawNominalType makeStructuralInterface(JSTypes commonTypes,
objKind = ObjectKind.UNRESTRICTED;
}
return new RawNominalType(
commonTypes, defSite, name, typeParameters, Kind.RECORD, objKind);
commonTypes, defSite, name, typeParameters, Kind.RECORD, objKind, false);
}

JSTypes getCommonTypes() {
Expand Down Expand Up @@ -196,6 +198,10 @@ public boolean isDict() {
return this.objectKind.isDict();
}

public boolean isAbstractClass() {
return this.isAbstractClass;
}

public boolean isFinalized() {
return this.isFinalized;
}
Expand Down Expand Up @@ -373,6 +379,12 @@ public JSType getProtoPropDeclaredType(String pname) {
return null;
}

public boolean hasAbstractMethod(String pname) {
Property p = getPropFromClass(pname);
JSType ptype = p == null ? null : p.getType();
return ptype != null && ptype.isFunctionType() && ptype.getFunType().isAbstract();
}

private Property getPropFromClass(String pname) {
Preconditions.checkState(isClass());
Property p = getOwnProp(pname);
Expand Down

0 comments on commit 1ab5aca

Please sign in to comment.