private void handleConstructorAnnotation( String functionName, Node funNode, RawNominalType constructorType, NominalType parentClass, ImmutableSet<NominalType> implementedIntfs, DeclaredTypeRegistry registry, FunctionTypeBuilder builder) { String className = constructorType.toString(); NominalType builtinObject = registry.getCommonTypes().getObjectType(); if (parentClass == null && !functionName.equals("Object")) { parentClass = builtinObject; } if (parentClass != null) { if (!constructorType.addSuperClass(parentClass)) { warnings.add(JSError.make(funNode, INHERITANCE_CYCLE, className)); } else if (parentClass != builtinObject) { if (constructorType.isStruct() && !parentClass.isStruct()) { warnings.add(JSError.make(funNode, CONFLICTING_SHAPE_TYPE, "struct", className)); } else if (constructorType.isDict() && !parentClass.isDict()) { warnings.add(JSError.make(funNode, CONFLICTING_SHAPE_TYPE, "dict", className)); } } } if (constructorType.isDict() && !implementedIntfs.isEmpty()) { warnings.add(JSError.make(funNode, DICT_IMPLEMENTS_INTERF, className)); } boolean noCycles = constructorType.addInterfaces(implementedIntfs); Preconditions.checkState(noCycles); builder.addNominalType(constructorType.getInstanceAsJSType()); }
public void resolveEnum(EnumType e, DeclaredTypeRegistry registry) { Preconditions.checkState( e != null, "getEnum should only be " + "called when we know that the enum is defined"); if (e.isResolved()) { return; } JSTypeExpression texp = e.getTypeExpr(); JSType enumeratedType; if (texp == null) { warnings.add(JSError.make(e.getTypeExprForErrorReporting().getRoot(), CIRCULAR_TYPEDEF_ENUM)); enumeratedType = JSType.UNKNOWN; } else { int numTypeVars = howmanyTypeVars; enumeratedType = getTypeFromJSTypeExpression(texp, registry, null); if (howmanyTypeVars > numTypeVars) { warnings.add(JSError.make(texp.getRoot(), ENUM_WITH_TYPEVARS)); enumeratedType = JSType.UNKNOWN; howmanyTypeVars = numTypeVars; } else if (enumeratedType.isTop()) { warnings.add(JSError.make(texp.getRoot(), ENUM_IS_TOP)); enumeratedType = JSType.UNKNOWN; } else if (enumeratedType.isUnion()) { warnings.add(JSError.make(texp.getRoot(), ENUM_IS_UNION)); enumeratedType = JSType.UNKNOWN; } } e.resolveEnum(enumeratedType); }
private NominalType getMaybeParentClass( JSDocInfo jsdoc, String functionName, Node funNode, ImmutableList<String> typeParameters, DeclaredTypeRegistry registry) { if (!jsdoc.hasBaseType()) { return null; } if (!jsdoc.isConstructor()) { warnings.add(JSError.make(funNode, EXTENDS_NOT_ON_CTOR_OR_INTERF, functionName)); return null; } Node docNode = jsdoc.getBaseType().getRoot(); JSType extendedType = getMaybeTypeFromComment(docNode, registry, typeParameters); if (extendedType == null) { return null; } NominalType parentClass = extendedType.getNominalTypeIfSingletonObj(); if (parentClass != null && parentClass.isClass()) { return parentClass; } if (parentClass == null) { warnings.add( JSError.make(funNode, EXTENDS_NON_OBJECT, functionName, extendedType.toString())); } else { Preconditions.checkState(parentClass.isInterface()); warnings.add(JSError.make(funNode, CONFLICTING_EXTENDED_TYPE, "constructor", functionName)); } return null; }
@Override public void println(CheckLevel level, JSError error) { switch (level) { case ERROR: Servlet.LOG.debug("error: " + error.toString()); break; case WARNING: Servlet.LOG.debug("warning: " + error.toString()); break; } }
private void fillInFunTypeBuilder( Node jsdocNode, RawNominalType ownerType, DeclaredTypeRegistry registry, ImmutableList<String> typeParameters, FunctionTypeBuilder builder) throws UnknownTypeException { Node child = jsdocNode.getFirstChild(); if (child.getType() == Token.THIS) { if (ownerType == null) { builder.addReceiverType(getThisOrNewType(child.getFirstChild(), registry, typeParameters)); } child = child.getNext(); } else if (child.getType() == Token.NEW) { Node newTypeNode = child.getFirstChild(); JSType t = getThisOrNewType(newTypeNode, registry, typeParameters); if (!t.isSubtypeOf(JSType.TOP_OBJECT) && (!t.hasTypeVariable() || t.hasScalar())) { warnings.add(JSError.make(newTypeNode, NEW_EXPECTS_OBJECT_OR_TYPEVAR, t.toString())); } builder.addNominalType(t); child = child.getNext(); } if (child.getType() == Token.PARAM_LIST) { for (Node arg = child.getFirstChild(); arg != null; arg = arg.getNext()) { try { switch (arg.getType()) { case Token.EQUALS: builder.addOptFormal( getTypeFromCommentHelper(arg.getFirstChild(), registry, typeParameters)); break; case Token.ELLIPSIS: Node restNode = arg.getFirstChild(); builder.addRestFormals( restNode == null ? JSType.UNKNOWN : getTypeFromCommentHelper(restNode, registry, typeParameters)); break; default: builder.addReqFormal(getTypeFromCommentHelper(arg, registry, typeParameters)); break; } } catch (FunctionTypeBuilder.WrongParameterOrderException e) { warnings.add(JSError.make(jsdocNode, WRONG_PARAMETER_ORDER)); builder.addPlaceholderFormal(); } } child = child.getNext(); } builder.addRetType(getTypeFromCommentHelper(child, registry, typeParameters)); }
/** * Consumes either a "classic" function jsdoc with @param, @return, etc, or a jsdoc with @type * {function ...} and finds the types of the formal parameters and the return value. It returns a * builder because the callers of this function must separately handle @constructor, @interface, * etc. * * <p>constructorType is non-null iff this function is a constructor or interface declaration. */ public FunctionAndSlotType getFunctionType( JSDocInfo jsdoc, String functionName, Node declNode, RawNominalType constructorType, RawNominalType ownerType, DeclaredTypeRegistry registry) { FunctionTypeBuilder builder = new FunctionTypeBuilder(); if (ownerType != null) { builder.addReceiverType(ownerType.getInstanceAsJSType()); } try { if (jsdoc != null && jsdoc.getType() != null) { JSType simpleType = getDeclaredTypeOfNode(jsdoc, ownerType, registry); if (simpleType.isUnknown() || simpleType.isTop()) { return qmarkFunctionDeclared; } FunctionType funType = simpleType.getFunType(); if (funType != null) { JSType slotType = simpleType.isFunctionType() ? null : simpleType; DeclaredFunctionType declType = funType.toDeclaredFunctionType(); if (ownerType != null && funType.getThisType() == null) { declType = declType.withReceiverType(ownerType.getInstanceAsJSType()); } return new FunctionAndSlotType(slotType, declType); } else { warnings.add(JSError.make(declNode, FUNCTION_WITH_NONFUNC_JSDOC)); jsdoc = null; } } DeclaredFunctionType declType = getFunTypeFromTypicalFunctionJsdoc( jsdoc, functionName, declNode, constructorType, ownerType, registry, builder); return new FunctionAndSlotType(null, declType); } catch (FunctionTypeBuilder.WrongParameterOrderException e) { warnings.add(JSError.make(declNode, WRONG_PARAMETER_ORDER)); return qmarkFunctionDeclared; } }
private void fillInFormalParameterTypes( JSDocInfo jsdoc, Node funNode, ImmutableList<String> typeParameters, DeclaredTypeRegistry registry, FunctionTypeBuilder builder, boolean ignoreJsdoc /* for when the jsdoc is malformed */) { boolean ignoreFunNode = !funNode.isFunction(); Node params = ignoreFunNode ? null : funNode.getSecondChild(); ParamIterator iterator = new ParamIterator(params, jsdoc); while (iterator.hasNext()) { String pname = iterator.nextString(); Node param = iterator.getNode(); ParameterKind p = ParameterKind.REQUIRED; if (param != null && convention.isOptionalParameter(param)) { p = ParameterKind.OPTIONAL; } else if (param != null && convention.isVarArgsParameter(param)) { p = ParameterKind.REST; } ParameterType inlineParamType = (ignoreJsdoc || ignoreFunNode || param.getJSDocInfo() == null) ? null : parseParameter(param.getJSDocInfo().getType(), p, registry, typeParameters); ParameterType fnParamType = inlineParamType; JSTypeExpression jsdocExp = jsdoc == null ? null : jsdoc.getParameterType(pname); if (jsdocExp != null) { if (inlineParamType == null) { fnParamType = parseParameter(jsdocExp, p, registry, typeParameters); } else { warnings.add(JSError.make(param, TWO_JSDOCS, "formal parameter " + pname)); } } JSType t = null; if (fnParamType != null) { p = fnParamType.kind; t = fnParamType.type; } switch (p) { case REQUIRED: builder.addReqFormal(t); break; case OPTIONAL: builder.addOptFormal(t); break; case REST: builder.addRestFormals(t != null ? t : JSType.UNKNOWN); break; } } }
private ImmutableSet<NominalType> getInterfacesHelper( JSDocInfo jsdoc, DeclaredTypeRegistry registry, ImmutableList<String> typeParameters, boolean implementedIntfs) { ImmutableSet.Builder<NominalType> builder = ImmutableSet.builder(); for (JSTypeExpression texp : (implementedIntfs ? jsdoc.getImplementedInterfaces() : jsdoc.getExtendedInterfaces())) { Node expRoot = texp.getRoot(); JSType interfaceType = getMaybeTypeFromComment(expRoot, registry, typeParameters); if (interfaceType != null) { NominalType nt = interfaceType.getNominalTypeIfSingletonObj(); if (nt != null && nt.isInterface()) { builder.add(nt); } else if (implementedIntfs) { warnings.add(JSError.make(expRoot, IMPLEMENTS_NON_INTERFACE, interfaceType.toString())); } else { warnings.add(JSError.make(expRoot, EXTENDS_NON_INTERFACE, interfaceType.toString())); } } } return builder.build(); }
@Override public CompilationError createCompilationError() { final int lineno = -1; final int charno = -1; JSError jsError = JSError.make( getInput().getName(), lineno, charno, CheckLevel.ERROR, MISSING_PROVIDE, getMissingProvide(), getInput().getName()); return new CompilationError(jsError); }
private void handleInterfaceAnnotation( JSDocInfo jsdoc, String functionName, Node funNode, RawNominalType constructorType, ImmutableSet<NominalType> implementedIntfs, ImmutableList<String> typeParameters, DeclaredTypeRegistry registry, FunctionTypeBuilder builder) { if (!implementedIntfs.isEmpty()) { warnings.add(JSError.make(funNode, CONFLICTING_IMPLEMENTED_TYPE, functionName)); } ImmutableSet<NominalType> extendedInterfaces = getExtendedInterfaces(jsdoc, registry, typeParameters); boolean noCycles = constructorType.addInterfaces( extendedInterfaces.isEmpty() ? ImmutableSet.of(registry.getCommonTypes().getObjectType()) : extendedInterfaces); if (!noCycles) { warnings.add(JSError.make(funNode, INHERITANCE_CYCLE, constructorType.toString())); } builder.addNominalType(constructorType.getInstanceAsJSType()); }
private JSType getNominalTypeHelper( RawNominalType rawType, Node n, DeclaredTypeRegistry registry, ImmutableList<String> outerTypeParameters) throws UnknownTypeException { NominalType uninstantiated = rawType.getAsNominalType(); if (!rawType.isGeneric() && !n.hasChildren()) { return rawType.getInstanceWithNullability(NULLABLE_TYPES_BY_DEFAULT); } ImmutableList.Builder<JSType> typeList = ImmutableList.builder(); if (n.hasChildren()) { // Compute instantiation of polymorphic class/interface. Preconditions.checkState(n.getFirstChild().isBlock(), n); for (Node child : n.getFirstChild().children()) { typeList.add(getTypeFromCommentHelper(child, registry, outerTypeParameters)); } } ImmutableList<JSType> typeArguments = typeList.build(); ImmutableList<String> typeParameters = rawType.getTypeParameters(); int typeArgsSize = typeArguments.size(); int typeParamsSize = typeParameters.size(); if (typeArgsSize != typeParamsSize) { // We used to also warn when (typeArgsSize < typeParamsSize), but it // happens so often that we stopped. Array, Object and goog.Promise are // common culprits, but many other types as well. if (typeArgsSize > typeParamsSize) { warnings.add( JSError.make( n, INVALID_GENERICS_INSTANTIATION, uninstantiated.getName(), String.valueOf(typeParamsSize), String.valueOf(typeArgsSize))); } return maybeMakeNullable( JSType.fromObjectType( ObjectType.fromNominalType( uninstantiated.instantiateGenerics( fixLengthOfTypeList(typeParameters.size(), typeArguments))))); } return maybeMakeNullable( JSType.fromObjectType( ObjectType.fromNominalType(uninstantiated.instantiateGenerics(typeArguments)))); }
public void resolveTypedef(Typedef td, DeclaredTypeRegistry registry) { Preconditions.checkState( td != null, "getTypedef should only be " + "called when we know that the typedef is defined"); if (td.isResolved()) { return; } JSTypeExpression texp = td.getTypeExpr(); JSType tdType; if (texp == null) { warnings.add( JSError.make(td.getTypeExprForErrorReporting().getRoot(), CIRCULAR_TYPEDEF_ENUM)); tdType = JSType.UNKNOWN; } else { tdType = getTypeFromJSTypeExpression(texp, registry, null); } td.resolveTypedef(tdType); }
private void fillInReturnType( JSDocInfo jsdoc, Node funNode, Node parent, ImmutableList<String> typeParameters, DeclaredTypeRegistry registry, FunctionTypeBuilder builder, boolean ignoreJsdoc /* for when the jsdoc is malformed */) { JSDocInfo inlineRetJsdoc = ignoreJsdoc ? null : funNode.getFirstChild().getJSDocInfo(); JSTypeExpression retTypeExp = jsdoc == null ? null : jsdoc.getReturnType(); if (parent.isSetterDef() && retTypeExp == null) { // inline returns for getters/setters are not parsed builder.addRetType(JSType.UNDEFINED); } else if (inlineRetJsdoc != null) { builder.addRetType(getDeclaredTypeOfNode(inlineRetJsdoc, registry, typeParameters)); if (retTypeExp != null) { warnings.add(JSError.make(funNode, TWO_JSDOCS, "the return type")); } } else { builder.addRetType(getTypeFromJSTypeExpression(retTypeExp, registry, typeParameters)); } }
private DeclaredFunctionType getFunTypeFromTypicalFunctionJsdoc( JSDocInfo jsdoc, String functionName, Node funNode, RawNominalType constructorType, RawNominalType ownerType, DeclaredTypeRegistry registry, FunctionTypeBuilder builder) { ImmutableList.Builder<String> typeParamsBuilder = ImmutableList.builder(); ImmutableList<String> typeParameters = ImmutableList.of(); Node parent = funNode.getParent(); // TODO(dimvar): need more @template warnings // - warn for multiple @template annotations // - warn for @template annotation w/out usage boolean ignoreJsdoc = false; if (jsdoc != null) { if (constructorType != null) { // We have created new names for these type variables in GTI, don't // create new ones here. typeParamsBuilder.addAll(constructorType.getTypeParameters()); } else { for (String typeParam : jsdoc.getTemplateTypeNames()) { typeParamsBuilder.add(this.nameGen.getNextName(typeParam)); } } // We don't properly support the type transformation language; we treat // its type variables as ordinary type variables. for (String typeParam : jsdoc.getTypeTransformations().keySet()) { typeParamsBuilder.add(this.nameGen.getNextName(typeParam)); } typeParameters = typeParamsBuilder.build(); if (!typeParameters.isEmpty()) { if (parent.isSetterDef() || parent.isGetterDef()) { ignoreJsdoc = true; jsdoc = null; warnings.add(JSError.make(funNode, TEMPLATED_GETTER_SETTER)); } else { builder.addTypeParameters(typeParameters); } } } if (ownerType != null) { typeParamsBuilder.addAll(ownerType.getTypeParameters()); typeParameters = typeParamsBuilder.build(); } fillInFormalParameterTypes(jsdoc, funNode, typeParameters, registry, builder, ignoreJsdoc); fillInReturnType(jsdoc, funNode, parent, typeParameters, registry, builder, ignoreJsdoc); if (jsdoc == null) { return builder.buildDeclaration(); } // Look at other annotations, eg, @constructor NominalType parentClass = getMaybeParentClass(jsdoc, functionName, funNode, typeParameters, registry); ImmutableSet<NominalType> implementedIntfs = getImplementedInterfaces(jsdoc, registry, typeParameters); if (constructorType == null && jsdoc.isConstructorOrInterface()) { // Anonymous type, don't register it. return builder.buildDeclaration(); } else if (jsdoc.isConstructor()) { handleConstructorAnnotation( functionName, funNode, constructorType, parentClass, implementedIntfs, registry, builder); } else if (jsdoc.isInterface()) { handleInterfaceAnnotation( jsdoc, functionName, funNode, constructorType, implementedIntfs, typeParameters, registry, builder); } else if (!implementedIntfs.isEmpty()) { warnings.add(JSError.make(funNode, IMPLEMENTS_WITHOUT_CONSTRUCTOR, functionName)); } if (jsdoc.hasThisType()) { Node thisRoot = jsdoc.getThisType().getRoot(); Preconditions.checkState(thisRoot.getType() == Token.BANG); builder.addReceiverType(getThisOrNewType(thisRoot.getFirstChild(), registry, typeParameters)); } return builder.buildDeclaration(); }
private JSType getTypeFromCommentHelper( Node n, DeclaredTypeRegistry registry, ImmutableList<String> typeParameters) throws UnknownTypeException { Preconditions.checkNotNull(n); if (typeParameters == null) { typeParameters = ImmutableList.of(); } switch (n.getType()) { case Token.LC: return getRecordTypeHelper(n, registry, typeParameters); case Token.EMPTY: // for function types that don't declare a return type return JSType.UNKNOWN; case Token.VOID: // TODO(dimvar): void can be represented in 2 ways: Token.VOID and a // Token.STRING whose getString() is "void". // Change jsdoc parsing to only have one representation. return JSType.UNDEFINED; case Token.LB: warnings.add(JSError.make(n, BAD_ARRAY_TYPE_SYNTAX)); return JSType.UNKNOWN; case Token.STRING: return getNamedTypeHelper(n, registry, typeParameters); case Token.PIPE: { // The way JSType.join works, Subtype|Supertype is equal to Supertype, // so when programmers write un-normalized unions, we normalize them // silently. We may also want to warn. JSType union = JSType.BOTTOM; for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // TODO(dimvar): When the union has many things, we join and throw // away types, except the result of the last join. Very inefficient. // Consider optimizing. JSType nextType = getTypeFromCommentHelper(child, registry, typeParameters); if (nextType.isUnknown()) { return JSType.UNKNOWN; } JSType nextUnion = JSType.join(union, nextType); if (nextUnion.isBottom()) { warnings.add( JSError.make(n, UNION_IS_UNINHABITABLE, nextType.toString(), union.toString())); return JSType.UNKNOWN; } union = nextUnion; } return union; } case Token.BANG: { JSType nullableType = getTypeFromCommentHelper(n.getFirstChild(), registry, typeParameters); if (nullableType.isTypeVariable()) { warnings.add(JSError.make(n, CANNOT_MAKE_TYPEVAR_NON_NULL)); } return nullableType.removeType(JSType.NULL); } case Token.QMARK: { Node child = n.getFirstChild(); if (child == null) { return JSType.UNKNOWN; } else { return JSType.join( JSType.NULL, getTypeFromCommentHelper(child, registry, typeParameters)); } } case Token.STAR: return JSType.TOP; case Token.FUNCTION: return getFunTypeHelper(n, registry, typeParameters); default: throw new IllegalArgumentException( "Unsupported type exp: " + Token.name(n.getType()) + " " + n.toStringTree()); } }