ObjectType substituteGenerics(Map<String, JSType> concreteTypes) {
   if (concreteTypes.isEmpty()) {
     return this;
   }
   PersistentMap<String, Property> newProps = PersistentMap.create();
   for (Map.Entry<String, Property> propsEntry : this.props.entrySet()) {
     String pname = propsEntry.getKey();
     Property newProp = propsEntry.getValue().substituteGenerics(concreteTypes);
     newProps = newProps.with(pname, newProp);
   }
   FunctionType newFn = fn == null ? null : fn.substituteGenerics(concreteTypes);
   return makeObjectType(
       nominalType == null ? null : nominalType.instantiateGenerics(concreteTypes),
       newProps,
       newFn,
       newFn != null && newFn.isQmarkFunction() || isLoose,
       objectKind);
 }