From bdcc4eb988786fb80d21b0667b2b68b2de5c6bbc Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 9 Jul 2015 21:44:56 -0700 Subject: [PATCH 01/17] Implementation for weak types --- src/compiler/checker.ts | 24 +++++++++++++++++++ .../diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 4 ++++ src/server/editorServices.ts | 2 +- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2b57aca48acc9..07265cf946206 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4785,9 +4785,14 @@ namespace ts { let result = Ternary.True; let properties = getPropertiesOfObjectType(target); let requireOptionalProperties = relation === subtypeRelation && !(source.flags & TypeFlags.ObjectLiteral); + let foundMatchingProperty = !isWeak(target); for (let targetProp of properties) { let sourceProp = getPropertyOfType(source, targetProp.name); + if (sourceProp) { + foundMatchingProperty = true; + } + if (sourceProp !== targetProp) { if (!sourceProp) { if (!(targetProp.flags & SymbolFlags.Optional) || requireOptionalProperties) { @@ -4859,6 +4864,14 @@ namespace ts { } } } + + if (!foundMatchingProperty && getPropertiesOfType(source).length > 0) { + if (reportErrors) { + reportError(Diagnostics.Weak_type_0_has_no_properties_in_common_with_1, typeToString(target), typeToString(source)); + } + return Ternary.False; + } + return result; } @@ -5121,6 +5134,17 @@ namespace ts { return false; } + // A type is 'weak' if it is an object type with at least one optional property + // and no required properties or index signatures + function isWeak(type: Type) { + let props = getPropertiesOfType(type); + return type.flags & TypeFlags.ObjectType && + props.length > 0 && + !forEach(props, p => !(p.flags & SymbolFlags.Optional)) && + !getIndexTypeOfType(type, IndexKind.String) && + !getIndexTypeOfType(type, IndexKind.Number); + } + function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { return compareProperties(sourceProp, targetProp, compareTypes) !== Ternary.False; } diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index a17f885759081..12b257eac3447 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -414,6 +414,7 @@ namespace ts { The_arguments_object_cannot_be_referenced_in_an_async_arrow_function_Consider_using_a_standard_async_function_expression: { code: 2522, category: DiagnosticCategory.Error, key: "The 'arguments' object cannot be referenced in an async arrow function. Consider using a standard async function expression." }, yield_expressions_cannot_be_used_in_a_parameter_initializer: { code: 2523, category: DiagnosticCategory.Error, key: "'yield' expressions cannot be used in a parameter initializer." }, await_expressions_cannot_be_used_in_a_parameter_initializer: { code: 2524, category: DiagnosticCategory.Error, key: "'await' expressions cannot be used in a parameter initializer." }, + Weak_type_0_has_no_properties_in_common_with_1: { code: 2525, category: DiagnosticCategory.Error, key: "Weak type '{0}' has no properties in common with '{1}'." }, JSX_element_attributes_type_0_must_be_an_object_type: { code: 2600, category: DiagnosticCategory.Error, key: "JSX element attributes type '{0}' must be an object type." }, The_return_type_of_a_JSX_element_constructor_must_return_an_object_type: { code: 2601, category: DiagnosticCategory.Error, key: "The return type of a JSX element constructor must return an object type." }, JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist: { code: 2602, category: DiagnosticCategory.Error, key: "JSX element implicitly has type 'any' because the global type 'JSX.Element' does not exist." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 94be4f53c9717..59c856e87d20d 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1645,6 +1645,10 @@ "category": "Error", "code": 2524 }, + "Weak type '{0}' has no properties in common with '{1}'.": { + "category": "Error", + "code": 2525 + }, "JSX element attributes type '{0}' must be an object type.": { "category": "Error", "code": 2600 diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 91d400ff26361..04cad8775618d 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -912,7 +912,7 @@ namespace ts.server { var dirPath = ts.getDirectoryPath(configFilename); var rawConfig: { config?: ProjectOptions; error?: Diagnostic; } = ts.readConfigFile(configFilename); if (rawConfig.error) { - return rawConfig.error; + return { errorMsg: ts.flattenDiagnosticMessageText(rawConfig.error.messageText, '\n') }; } else { var parsedCommandLine = ts.parseConfigFile(rawConfig.config, this.host, dirPath); From 3c76c3e474256e6694f843cbb4c67318260b55d5 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 9 Jul 2015 21:46:59 -0700 Subject: [PATCH 02/17] Baselines for weak types --- ...atWithObjectMembersOptionality2.errors.txt | 41 +- .../subtypingWithObjectMembers5.errors.txt | 17 +- .../reference/underscoreTest1.errors.txt | 907 +++ .../reference/underscoreTest1.symbols | 4898 -------------- .../baselines/reference/underscoreTest1.types | 5659 ----------------- tests/baselines/reference/weakType.js | 21 + tests/baselines/reference/weakType.symbols | 27 + tests/baselines/reference/weakType.types | 30 + tests/cases/compiler/weakType.ts | 12 + 9 files changed, 1053 insertions(+), 10559 deletions(-) create mode 100644 tests/baselines/reference/underscoreTest1.errors.txt delete mode 100644 tests/baselines/reference/underscoreTest1.symbols delete mode 100644 tests/baselines/reference/underscoreTest1.types create mode 100644 tests/baselines/reference/weakType.js create mode 100644 tests/baselines/reference/weakType.symbols create mode 100644 tests/baselines/reference/weakType.types create mode 100644 tests/cases/compiler/weakType.ts diff --git a/tests/baselines/reference/assignmentCompatWithObjectMembersOptionality2.errors.txt b/tests/baselines/reference/assignmentCompatWithObjectMembersOptionality2.errors.txt index de2751beacaba..e8d6b32d21c32 100644 --- a/tests/baselines/reference/assignmentCompatWithObjectMembersOptionality2.errors.txt +++ b/tests/baselines/reference/assignmentCompatWithObjectMembersOptionality2.errors.txt @@ -1,3 +1,18 @@ +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(33,5): error TS2322: Type 'D' is not assignable to type 'C'. + Weak type 'C' has no properties in common with 'D'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(34,5): error TS2322: Type 'E' is not assignable to type 'C'. + Weak type 'C' has no properties in common with 'E'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(35,5): error TS2322: Type 'F' is not assignable to type 'C'. + Weak type 'C' has no properties in common with 'F'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(38,5): error TS2322: Type 'D' is not assignable to type '{ opt?: Base; }'. + Weak type '{ opt?: Base; }' has no properties in common with 'D'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(39,5): error TS2322: Type 'E' is not assignable to type '{ opt?: Base; }'. + Weak type '{ opt?: Base; }' has no properties in common with 'E'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(40,5): error TS2322: Type 'F' is not assignable to type '{ opt?: Base; }'. + Weak type '{ opt?: Base; }' has no properties in common with 'F'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(43,5): error TS2322: Type 'D' is not assignable to type '{ opt?: Base; }'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(44,5): error TS2322: Type 'E' is not assignable to type '{ opt?: Base; }'. +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(45,5): error TS2322: Type 'F' is not assignable to type '{ opt?: Base; }'. tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(74,5): error TS2322: Type 'D' is not assignable to type 'C'. Property 'opt' is missing in type 'D'. tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts(75,5): error TS2322: Type 'E' is not assignable to type 'C'. @@ -18,7 +33,7 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme Property 'opt' is missing in type 'F'. -==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts (9 errors) ==== +==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithObjectMembersOptionality2.ts (18 errors) ==== // M is optional and S contains no property with the same name as M // N is optional and T contains no property with the same name as N @@ -52,18 +67,42 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme // all ok c = d; + ~ +!!! error TS2322: Type 'D' is not assignable to type 'C'. +!!! error TS2322: Weak type 'C' has no properties in common with 'D'. c = e; + ~ +!!! error TS2322: Type 'E' is not assignable to type 'C'. +!!! error TS2322: Weak type 'C' has no properties in common with 'E'. c = f; + ~ +!!! error TS2322: Type 'F' is not assignable to type 'C'. +!!! error TS2322: Weak type 'C' has no properties in common with 'F'. c = a; a = d; + ~ +!!! error TS2322: Type 'D' is not assignable to type '{ opt?: Base; }'. +!!! error TS2322: Weak type '{ opt?: Base; }' has no properties in common with 'D'. a = e; + ~ +!!! error TS2322: Type 'E' is not assignable to type '{ opt?: Base; }'. +!!! error TS2322: Weak type '{ opt?: Base; }' has no properties in common with 'E'. a = f; + ~ +!!! error TS2322: Type 'F' is not assignable to type '{ opt?: Base; }'. +!!! error TS2322: Weak type '{ opt?: Base; }' has no properties in common with 'F'. a = c; b = d; + ~ +!!! error TS2322: Type 'D' is not assignable to type '{ opt?: Base; }'. b = e; + ~ +!!! error TS2322: Type 'E' is not assignable to type '{ opt?: Base; }'. b = f; + ~ +!!! error TS2322: Type 'F' is not assignable to type '{ opt?: Base; }'. b = a; b = c; } diff --git a/tests/baselines/reference/subtypingWithObjectMembers5.errors.txt b/tests/baselines/reference/subtypingWithObjectMembers5.errors.txt index 7fd025721ec27..f42947c6eff6f 100644 --- a/tests/baselines/reference/subtypingWithObjectMembers5.errors.txt +++ b/tests/baselines/reference/subtypingWithObjectMembers5.errors.txt @@ -4,9 +4,15 @@ tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingW Property '1' is missing in type 'B2'. tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithObjectMembers5.ts(32,11): error TS2420: Class 'B3' incorrectly implements interface 'A3'. Property ''1'' is missing in type 'B3'. +tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithObjectMembers5.ts(43,11): error TS2420: Class 'B' incorrectly implements interface 'A'. + Weak type 'A' has no properties in common with 'B'. +tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithObjectMembers5.ts(51,11): error TS2420: Class 'B2' incorrectly implements interface 'A2'. + Weak type 'A2' has no properties in common with 'B2'. +tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithObjectMembers5.ts(59,11): error TS2420: Class 'B3' incorrectly implements interface 'A3'. + Weak type 'A3' has no properties in common with 'B3'. -==== tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithObjectMembers5.ts (3 errors) ==== +==== tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingWithObjectMembers5.ts (6 errors) ==== interface Base { foo: string; } @@ -59,6 +65,9 @@ tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingW } class B implements A { + ~ +!!! error TS2420: Class 'B' incorrectly implements interface 'A'. +!!! error TS2420: Weak type 'A' has no properties in common with 'B'. fooo: Derived; // ok } @@ -67,6 +76,9 @@ tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingW } class B2 implements A2 { + ~~ +!!! error TS2420: Class 'B2' incorrectly implements interface 'A2'. +!!! error TS2420: Weak type 'A2' has no properties in common with 'B2'. 2: Derived; // ok } @@ -75,6 +87,9 @@ tests/cases/conformance/types/typeRelationships/subtypesAndSuperTypes/subtypingW } class B3 implements A3 { + ~~ +!!! error TS2420: Class 'B3' incorrectly implements interface 'A3'. +!!! error TS2420: Weak type 'A3' has no properties in common with 'B3'. '1.0': Derived; // ok } } \ No newline at end of file diff --git a/tests/baselines/reference/underscoreTest1.errors.txt b/tests/baselines/reference/underscoreTest1.errors.txt new file mode 100644 index 0000000000000..cdb7991a04e62 --- /dev/null +++ b/tests/baselines/reference/underscoreTest1.errors.txt @@ -0,0 +1,907 @@ +tests/cases/compiler/underscoreTest1_underscoreTests.ts(252,66): error TS2345: Argument of type '{ variable: string; }' is not assignable to parameter of type 'TemplateSettings'. + Weak type 'TemplateSettings' has no properties in common with '{ variable: string; }'. + + +==== tests/cases/compiler/underscoreTest1_underscoreTests.ts (1 errors) ==== + /// + + declare var $; + declare function alert(x: string): void; + + _.each([1, 2, 3], (num) => alert(num.toString())); + _.each({ one: 1, two: 2, three: 3 }, (value: number, key?: string) => alert(value.toString())); + + _.map([1, 2, 3], (num) => num * 3); + _.map({ one: 1, two: 2, three: 3 }, (value: number, key?: string) => value * 3); + + var sum = _.reduce([1, 2, 3], (memo, num) => memo + num, 0); + + var list = [[0, 1], [2, 3], [4, 5]]; + var flat = _.reduceRight(list, (a, b) => a.concat(b), []); + + var even = _.find([1, 2, 3, 4, 5, 6], (num) => num % 2 == 0); + + var evens = _.filter([1, 2, 3, 4, 5, 6], (num) => num % 2 == 0); + + var listOfPlays = [{ title: "Cymbeline", author: "Shakespeare", year: 1611 }, { title: "The Tempest", author: "Shakespeare", year: 1611 }, { title: "Other", author: "Not Shakespeare", year: 2012 }]; + _.where(listOfPlays, { author: "Shakespeare", year: 1611 }); + + var odds = _.reject([1, 2, 3, 4, 5, 6], (num) => num % 2 == 0); + + _.all([true, 1, null, 'yes'], _.identity); + + _.any([null, 0, 'yes', false]); + + _.contains([1, 2, 3], 3); + + _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + + var stooges = [{ name: 'moe', age: 40 }, { name: 'larry', age: 50 }, { name: 'curly', age: 60 }]; + _.pluck(stooges, 'name'); + + _.max(stooges, (stooge) => stooge.age); + + var numbers = [10, 5, 100, 2, 1000]; + _.min(numbers); + + _.sortBy([1, 2, 3, 4, 5, 6], (num) => Math.sin(num)); + + + // not sure how this is typechecking at all.. Math.floor(e) is number not string..? + _([1.3, 2.1, 2.4]).groupBy((e: number, i?: number, list?: number[]) => Math.floor(e)); + _.groupBy([1.3, 2.1, 2.4], (num: number) => Math.floor(num)); + _.groupBy(['one', 'two', 'three'], 'length'); + + _.countBy([1, 2, 3, 4, 5], (num) => num % 2 == 0 ? 'even' : 'odd'); + + _.shuffle([1, 2, 3, 4, 5, 6]); + + // (function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4); + + _.size({ one: 1, two: 2, three: 3 }); + + /////////////////////////////////////////////////////////////////////////////////////// + + _.first([5, 4, 3, 2, 1]); + _.initial([5, 4, 3, 2, 1]); + _.last([5, 4, 3, 2, 1]); + _.rest([5, 4, 3, 2, 1]); + _.compact([0, 1, false, 2, '', 3]); + + _.flatten([1, 2, 3, 4]); + _.flatten([1, [2]]); + + // typescript doesn't like the elements being different + _.flatten([1, [2], [3, [[4]]]]); + _.flatten([1, [2], [3, [[4]]]], true); + _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); + _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); + _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); + _.difference([1, 2, 3, 4, 5], [5, 2, 10]); + _.uniq([1, 2, 1, 3, 1, 4]); + _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); + _.object(['moe', 'larry', 'curly'], [30, 40, 50]); + _.object([['moe', 30], ['larry', 40], ['curly', 50]]); + _.indexOf([1, 2, 3], 2); + _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); + _.sortedIndex([10, 20, 30, 40, 50], 35); + _.range(10); + _.range(1, 11); + _.range(0, 30, 5); + _.range(0, 30, 5); + _.range(0); + + /////////////////////////////////////////////////////////////////////////////////////// + + var func = function (greeting) { return greeting + ': ' + this.name }; + // need a second var otherwise typescript thinks func signature is the above func type, + // instead of the newly returned _bind => func type. + var func2 = _.bind(func, { name: 'moe' }, 'hi'); + func2(); + + var buttonView = { + label: 'underscore', + onClick: function () { alert('clicked: ' + this.label); }, + onHover: function () { alert('hovering: ' + this.label); } + }; + _.bindAll(buttonView); + $('#underscore_button').bind('click', buttonView.onClick); + + var fibonacci = _.memoize(function (n) { + return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + }); + + var log = _.bind((message?: string, ...rest: string[]) => { }, Date); + _.delay(log, 1000, 'logged later'); + + _.defer(function () { alert('deferred'); }); + + var updatePosition = () => alert('updating position...'); + var throttled = _.throttle(updatePosition, 100); + $(null).scroll(throttled); + + var calculateLayout = () => alert('calculating layout...'); + var lazyLayout = _.debounce(calculateLayout, 300); + $(null).resize(lazyLayout); + + var createApplication = () => alert('creating application...'); + var initialize = _.once(createApplication); + initialize(); + initialize(); + + var notes: any[]; + var render = () => alert("rendering..."); + var renderNotes = _.after(notes.length, render); + _.each(notes, (note) => note.asyncSave({ success: renderNotes })); + + var hello = function (name) { return "hello: " + name; }; + hello = _.wrap(hello, (func, arg) => { return "before, " + func(arg) + ", after"; }); + hello("moe"); + + var greet = function (name) { return "hi: " + name; }; + var exclaim = function (statement) { return statement + "!"; }; + var welcome = _.compose(exclaim, greet); + welcome('moe'); + + /////////////////////////////////////////////////////////////////////////////////////// + + _.keys({ one: 1, two: 2, three: 3 }); + _.values({ one: 1, two: 2, three: 3 }); + _.pairs({ one: 1, two: 2, three: 3 }); + _.invert({ Moe: "Moses", Larry: "Louis", Curly: "Jerome" }); + _.functions(_); + _.extend({ name: 'moe' }, { age: 50 }); + _.pick({ name: 'moe', age: 50, userid: 'moe1' }, 'name', 'age'); + _.omit({ name: 'moe', age: 50, userid: 'moe1' }, 'userid'); + + var iceCream = { flavor: "chocolate" }; + _.defaults(iceCream, { flavor: "vanilla", sprinkles: "lots" }); + + _.clone({ name: 'moe' }); + + _.chain([1, 2, 3, 200]) + .filter(function (num) { return num % 2 == 0; }) + .tap(alert) + .map(function (num) { return num * num }) + .value(); + + _.has({ a: 1, b: 2, c: 3 }, "b"); + + var moe = { name: 'moe', luckyNumbers: [13, 27, 34] }; + var clone = { name: 'moe', luckyNumbers: [13, 27, 34] }; + moe == clone; + _.isEqual(moe, clone); + + _.isEmpty([1, 2, 3]); + _.isEmpty({}); + + _.isElement($('body')[0]); + + (function () { return _.isArray(arguments); })(); + _.isArray([1, 2, 3]); + + _.isObject({}); + _.isObject(1); + + + // (() => { return _.isArguments(arguments); })(1, 2, 3); + _.isArguments([1, 2, 3]); + + _.isFunction(alert); + + _.isString("moe"); + + _.isNumber(8.4 * 5); + + _.isFinite(-101); + + _.isFinite(-Infinity); + + _.isBoolean(null); + + _.isDate(new Date()); + + _.isRegExp(/moe/); + + _.isNaN(NaN); + isNaN(undefined); + _.isNaN(undefined); + + _.isNull(null); + _.isNull(undefined); + + _.isUndefined((null).missingVariable); + + /////////////////////////////////////////////////////////////////////////////////////// + + var underscore = _.noConflict(); + + var moe2 = { name: 'moe' }; + moe2 === _.identity(moe); + + var genie; + + _.times(3, function (n) { genie.grantWishNumber(n); }); + + _.random(0, 100); + + _.mixin({ + capitalize: function (string) { + return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); + } + }); + (_("fabio")).capitalize(); + + _.uniqueId('contact_'); + + _.escape('Curly, Larry & Moe'); + + var object = { cheese: 'crumpets', stuff: function () { return 'nonsense'; } }; + _.result(object, 'cheese'); + + _.result(object, 'stuff'); + + var compiled = _.template("hello: <%= name %>"); + compiled({ name: 'moe' }); + var list2 = "<% _.each(people, function(name) { %>
  • <%= name %>
  • <% }); %>"; + _.template(list2, { people: ['moe', 'curly', 'larry'] }); + var template = _.template("<%- value %>"); + template({ value: '