/*
 * Copyright 2009 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 static com.google.javascript.rhino.testing.BaseJSTypeTestCase.ALL_NATIVE_EXTERN_TYPES;

import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.ObjectType;

import java.util.List;

/**
 * Unit tests for {@link FunctionTypeBuilder}.
 *
 */
public final class FunctionTypeBuilderTest extends CompilerTestCase {

  public FunctionTypeBuilderTest() {
    parseTypeInfo = true;
    enableTypeCheck();
  }

  @Override
  public CompilerPass getProcessor(Compiler compiler) {
    // By turning on type checking, the FunctionTypeBuilder will be invoked.
    return new CompilerPass() {
          @Override
          public void process(Node externs, Node js) {}
        };
  }

  @Override
  public int getNumRepetitions() {
    return 1;
  }

  public void testValidBuiltInTypeRedefinition() throws Exception {
    testSame(ALL_NATIVE_EXTERN_TYPES, "", null);
  }

  public void testBuiltInTypeDifferentReturnType() throws Exception {
    testSame(
        "/**\n"
        + " * @constructor\n"
        + " * @param {*} opt_str\n"
        + " * @return {number}\n"
        + " */\n"
        + "function String(opt_str) {}\n",
        "", FunctionTypeBuilder.TYPE_REDEFINITION,
        "attempted re-definition of type String\n"
        + "found   : function (new:String, *=): number\n"
        + "expected: function (new:String, *=): string");
  }

  public void testBuiltInTypeDifferentNumParams() throws Exception {
    testSame(
        "/**\n"
        + " * @constructor\n"
        + " * @return {string}\n"
        + " */\n"
        + "function String() {}\n",
        "", FunctionTypeBuilder.TYPE_REDEFINITION,
        "attempted re-definition of type String\n"
        + "found   : function (new:String): string\n"
        + "expected: function (new:String, *=): string");
  }

  public void testBuiltInTypeDifferentNumParams2() throws Exception {
    testSame(
        "/**\n"
        + " * @constructor\n"
        + " * @return {string}\n"
        + " */\n"
        + "function String(opt_str, opt_nothing) {}\n",
        "", FunctionTypeBuilder.TYPE_REDEFINITION,
        "attempted re-definition of type String\n"
        + "found   : function (new:String, ?=, ?=): string\n"
        + "expected: function (new:String, *=): string");
  }

  public void testBuiltInTypeDifferentParamType() throws Exception {
    testSame(
        "/**\n"
        + " * @constructor\n"
        + " * @return {string}\n"
        + " */\n"
        + "function String(opt_str) {}\n",
        "", FunctionTypeBuilder.TYPE_REDEFINITION,
        "attempted re-definition of type String\n"
        + "found   : function (new:String, ?=): string\n"
        + "expected: function (new:String, *=): string");
  }

  public void testBadFunctionTypeDefinition() throws Exception {
    testSame(
        "/** @constructor */function Function(opt_str) {}\n",
        "", FunctionTypeBuilder.TYPE_REDEFINITION,
        "attempted re-definition of type Function\n"
        + "found   : function (new:Function, ?=): ?\n"
        + "expected: function (new:Function, ...*): ?");
  }

  public void testInlineJsDoc() throws Exception {
    testSame(
        "/** @return {number} */ function f(/** string */ x) { return x; }", "",
        TypeValidator.TYPE_MISMATCH_WARNING,
        "inconsistent return type\n" +
        "found   : string\n" +
        "required: number");
  }

  public void testInlineJsDoc2() throws Exception {
    testSame(
        "/** @return {T} \n @template T */ " +
        "function f(/** T */ x) { return x; }" +
        "/** @type {string} */ var x = f(1);", "",
        TypeValidator.TYPE_MISMATCH_WARNING,
        "initializing variable\n" +
        "found   : number\n" +
        "required: string");
  }

  public void testExternSubTypes() throws Exception {
    testSame(ALL_NATIVE_EXTERN_TYPES, "", null);

    List<FunctionType> subtypes = ((ObjectType) getLastCompiler()
        .getTypeRegistry().getType("Error")).getConstructor().getSubTypes();
    for (FunctionType type : subtypes) {
      String typeName = type.getInstanceType().toString();
      FunctionType typeInRegistry = ((ObjectType) getLastCompiler()
          .getTypeRegistry().getType(typeName)).getConstructor();
      assertSame(type, typeInRegistry);
    }
  }
}
