google / google/closure-compiler
Should fail on mis-assignment to @struct @record
- Dominant language
- JavaScript
- Stars
- 7.7k
- Forks
- 1.2k
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 6
Description
Given this:
``` javascript
/**
* @struct @record
*/
function R() {
/** @type {string} */
this.s;
}
let /** !R */ m;
m = { s: "s" }; // succeeds
//m = { }; // fails, missing prop
//m = { s: 5 }; // fails, wrong type
m = { s: "s", bad: true }; // should fail, unknown prop in struct
```
[Debugger](https://closure-compiler-debugger.appspot.com/#input0%3D%252F**%250A%2520*%2520%2540struct%2520%2540record%250A%2520*%252F%250Afunction%2520R()%2520%257B%250A%2520%2520%252F**%2520%2540type%2520%257Bstring%257D%2520*%252F%250A%2520%2520this.s%253B%250A%257D%250A%250Alet%2520%252F**%2520!R%2520*%252F%2520m%253B%250Am%2520%253D%2520%257B%2520s%253A%2520%2522s%2522%2520%257D%253B%2520%252F%252F%2520succeeds%250A%252F%252Fm%2520%253D%2520%257B%2520%257D%253B%2520%252F%252F%2520fails%252C%2520missing%2520prop%250A%252F%252Fm%2520%253D%2520%257B%2520s%253A%25205%2520%257D%253B%2520%252F%252F%2520fails%252C%2520wrong%2520type%250Am%2520%253D%2520%257B%2520s%253A%2520%2522s%2522%252C%2520bad%253A%2520true%2520%257D%253B%2520%252F%252F%2520should%2520fail%252C%2520unknown%2520prop%2520in%2520struct%250A%26input1%26conformanceConfig%26externs%26refasterjs-template%26includeDefaultExterns%3Dtrue%26CHECK_TYPES%3Dtrue%26TRANSPILE%3Dtrue%26CLOSURE_PASS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue)
The last line should ideally fail. I see this no different to:
``` javascript
let /** !R */ m = { s: "s" };
m.bad = true;
```
which does fail: `Cannot add a property to a struct instance after it is constructed.`
I propose a PR with: ObjectType.js amended with "But, if...":
``` javascript
private static boolean isStructuralSubtypeHelper(
ObjectType typeA, ObjectType typeB,
ImplCache implicitImplCache, SubtypingMode subtypingMode) {
// typeA is a subtype of record type typeB iff:
// 1) typeA has all the non-optional properties declared in typeB.
// 2) And for each property of typeB, its type must be
// a super type of the corresponding property of typeA.
for (String property : typeB.getPropertyNames()) {
JSType propB = typeB.getPropertyType(property);
if (!typeA.hasProperty(property)) {
// Currently, any type that explicitly includes undefined (eg, `?|undefined`) is optional.
if (propB.isExplicitlyVoidable()) {
continue;
}
return false;
}
JSType propA = typeA.getPropertyType(property);
if (!propA.isSubtype(propB, implicitImplCache, subtypingMode)) {
return false;
}
}
// But, if typeB is @struct, disallow additional unknown properties:
if (typeB.isStruct()) {
for (String property : typeA.getPropertyNames()) {
if (!typeB.hasProperty(property)) return false;
}
}
return true;
}
```
Let me know if this is likely to be accepted.
Contributor guide
Assessment
This issue has not been assessed yet.