File: src/com/google/javascript/jscomp/TypedScopeCreator.java
Issue: Type of prototype property incorrectly inferred to string
What steps will reproduce the problem?
1. Compile the following code:

/** @param {Object} a */
function f(a) {
  a.prototype = '__proto';
}

/** @param {Object} a */
function g(a) {
  a.prototype = function(){};
}

What is the expected output? What do you see instead?

Should type check. Instead, gives error:

WARNING - assignment to property prototype of Object
found   : function (): undefined
required: string
  a.prototype = function(){};
  ^

Buggy code:
private boolean isQualifiedNameInferred(
    String qName, Node n, JSDocInfo info,
    Node rhsValue, JSType valueType) {
  if (valueType == null) {
    return true;
  }

  // Prototypes of constructors and interfaces are always declared.
  if (qName != null && qName.endsWith(".prototype")) {
      return false;
  }

  boolean inferred = true;
  if (info != null) {
    inferred = !(info.hasType()
        || info.hasEnumParameterType()
        || (isConstantSymbol(info, n) && valueType != null
            && !valueType.isUnknownType())
        || FunctionTypeBuilder.isFunctionTypeDeclaration(info));
  }

  if (inferred && rhsValue != null && rhsValue.isFunction()) {
    if (info != null) {
      return false;
    } else if (!scope.isDeclared(qName, false) &&
        n.isUnscopedQualifiedName()) {

      // Check if this is in a conditional block.
      // Functions assigned in conditional blocks are inferred.
      for (Node current = n.getParent();
           !(current.isScript() || current.isFunction());
           current = current.getParent()) {
        if (NodeUtil.isControlStructure(current)) {
          return true;
        }
      }

      // Check if this is assigned in an inner scope.
      // Functions assigned in inner scopes are inferred.
      AstFunctionContents contents =
          getFunctionAnalysisResults(scope.getRootNode());
      if (contents == null ||
          !contents.getEscapedQualifiedNames().contains(qName)) {
        return false;
      }
    }
  }
  return inferred;
}

Fixed code:
private boolean isQualifiedNameInferred(
    String qName, Node n, JSDocInfo info,
    Node rhsValue, JSType valueType) {
  if (valueType == null) {
    return true;
  }

  // Prototypes of constructors and interfaces are always declared.
  if (qName != null && qName.endsWith(".prototype")) {
    String className = qName.substring(0, qName.lastIndexOf(".prototype"));
    Var slot = scope.getSlot(className);
    JSType classType = slot == null ? null : slot.getType();
    if (classType != null
        && (classType.isConstructor() || classType.isInterface())) {
      return false;
    }
  }

  boolean inferred = true;
  if (info != null) {
    inferred = !(info.hasType()
        || info.hasEnumParameterType()
        || (isConstantSymbol(info, n) && valueType != null
            && !valueType.isUnknownType())
        || FunctionTypeBuilder.isFunctionTypeDeclaration(info));
  }

  if (inferred && rhsValue != null && rhsValue.isFunction()) {
    if (info != null) {
      return false;
    } else if (!scope.isDeclared(qName, false) &&
        n.isUnscopedQualifiedName()) {

      // Check if this is in a conditional block.
      // Functions assigned in conditional blocks are inferred.
      for (Node current = n.getParent();
           !(current.isScript() || current.isFunction());
           current = current.getParent()) {
        if (NodeUtil.isControlStructure(current)) {
          return true;
        }
      }

      // Check if this is assigned in an inner scope.
      // Functions assigned in inner scopes are inferred.
      AstFunctionContents contents =
          getFunctionAnalysisResults(scope.getRootNode());
      if (contents == null ||
          !contents.getEscapedQualifiedNames().contains(qName)) {
        return false;
      }
    }
  }
  return inferred;
}