Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weak type detection #3842

Closed
wants to merge 2 commits into from
Closed

Conversation

RyanCavanaugh
Copy link
Member

This implements the other suggestion from @ahejlsberg in #3755, and fixes a compiler bug found by the rule (in editorServices.ts).

A weak type is one which has only optional properties and is not empty. Because these types are assignable from anything except types with matching non-assignable properties, they are very weakly typechecked. The simple fix here is to require that a type is only assignable to a weak type if they are not completely disjoint.

Example:

interface TextOptions {
    alignment?: string;
    color?: string;
    padding?: number;
}
function drawText(opts: TextOptions) { ... }

// All of these are errors under this PR

// Also error under #3823
drawText({ align: 'center'} ); 

// Also error under #3823
function getDefaultOptions(): TextOptions {
    return { colour: 'blue'; };
}

// Not covered by #3823, but is covered in this PR
drawText(getDefaultOptions);
// Not covered by #3823, but is covered in this PR
drawText(getDefaultOptions());
// Not covered by #3823, but is covered in this PR
drawText(42);

@danquirk
Copy link
Member

Note this would be a breaking change.

@JsonFreeman
Copy link
Contributor

👍

@mhegazy
Copy link
Contributor

mhegazy commented Aug 6, 2015

@RyanCavanaugh can we close this.

@RyanCavanaugh
Copy link
Member Author

Need to discuss at a design meeting to get a 👍 or 👎 on this

@xLama
Copy link

xLama commented Aug 26, 2015

What about this?

interface TextOptions {
    alignment?: string;
    color?: string;
    padding?: number;
    length?: number;
}
function drawText(opts: TextOptions) { ... }
drawText( [] );

Would it show an error?

@RyanCavanaugh
Copy link
Member Author

@xLama that would not be an error due to the corresponding length properties in both types.

@xLama
Copy link

xLama commented Aug 27, 2015

👍

@xLama
Copy link

xLama commented Sep 2, 2015

Will this be in 1.6?

@rotemdan
Copy link

rotemdan commented Sep 3, 2015

Just encountered this:

let intendedToBeANonPrimitive: { val1?: number, val2?: string };
intendedToBeANonPrimitive = 45.234234; // Works?!

If the intention of the programmer was to allow the value to be assignable from a primitive, they would never have defined properties on it, either optional or required. If that was actually desirable, it is possible to use a union:

let canAlsoBeANumber: { val1?: number, val2?: string } | number;
canAlsoBeANumber= 45.234234; // OK

I encounter this problem very frequently with function "options" arguments, which are commonly defined to have all-optional properties.

I frequently convert an old API that looks something like this:

function getValues(startIndex?: number, endIndex?: number)

To a new one accepting an options object instead:

function getValues(options: { startIndex?: number, endIndex?: number })

Then unexpectedly I don't get any error on incorrect calls like these:

getValues(456);

And have to track them manually.

It would be great to have this fixed already in the 1.6 release. I personally consider this to be a bug. Anyway, keep up the great work :).

@DanielRosenwasser
Copy link
Member

That issue surfaces pretty badly when .d.ts authors order overloads incorrectly and order an overload taking a string after an overload taking an options bag.

@JsonFreeman
Copy link
Contributor

@DanielRosenwasser While I agree it's a bad problem, I don't think the overload ordering makes it that much worse because the initial pass of overload resolution uses subtype, not assignability. Subtype is tighter than assignability, and actually requires optional properties in the target to be present in the source, except when the source is an object literal. So a primitive, when passed to a function that expects an options bag, would fail subtype.

@DanielRosenwasser
Copy link
Member

One temporary workaround I gave a user was the following

interface NoPrimitives {
    valueOf?: { __DoNotUse: {} }; // Incompatible with 'valueOf' in strings, numbers, and booleans
    test?: { __DoNotUse: {} }; // Incompatible with 'test' in regexes
}

 interface Schema extends NoPrimitives {
  type?: string;
  items?: Schema | Schema[];
  properties?: {[property: string]: Schema};
}

@kitsonk
Copy link
Contributor

kitsonk commented Mar 11, 2016

This appears to have been closed without merging (or at least it doesn't appear to be working re: #7485). Was that intended?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 12, 2016

the issue still remains. the proposal is still under consideration, but there were no immediate plans to pursue it, that is why it is closed.

@kitsonk
Copy link
Contributor

kitsonk commented Mar 12, 2016

Is there an issue with a proposal that is open?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 13, 2016

Not that i am aware of. @RyanCavanaugh ?

@RyanCavanaugh
Copy link
Member Author

Nope.

@sandersn
Copy link
Member

I merged master with this PR and immediately found an incorrect argument being passed from checker. I'll create a new PR based on a branch in Microsoft/TypeScript when I get all the bugs fixed and tests passing.

@sandersn
Copy link
Member

sandersn commented May 22, 2017

I found an additional case that we may not want to report errors for:

class A { a? }
declare function foo(a: A): void;
function bar(faux: Object) {
  foo(faux) // Error: Object doesn't have any properties in common with weak type A
}

The author should have used faux: {} to avoid this error, but lots of our users don't know that.

The real example is more believable because foo is a type guard:

class A { a? }
class B extends A { b }
declare function isB(a: A): a is B;
function bar(faux: Object) {
  if (isB(faux)) { // Error: Object doesn't have any properties in common with weak type A
    // sad code ... should have said `faux: A` or `isB(a: any)`
  }
}

@sandersn sandersn mentioned this pull request May 23, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants