|
38 | 38 | import static java.util.stream.Collectors.joining;
|
39 | 39 |
|
40 | 40 | import com.google.common.annotations.VisibleForTesting;
|
| 41 | +import com.google.common.collect.ImmutableList; |
41 | 42 | import com.google.common.collect.ImmutableSet;
|
42 | 43 | import com.google.common.collect.Iterables;
|
43 | 44 | import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
|
67 | 68 | import com.google.javascript.rhino.jstype.TemplateType;
|
68 | 69 | import com.google.javascript.rhino.jstype.TemplateTypeMap;
|
69 | 70 | import com.google.javascript.rhino.jstype.TemplatizedType;
|
| 71 | +import com.google.javascript.rhino.jstype.UnionType; |
70 | 72 | import java.util.HashMap;
|
71 | 73 | import java.util.HashSet;
|
72 | 74 | import java.util.Iterator;
|
@@ -316,56 +318,62 @@ public final class TypeCheck implements NodeTraversal.Callback, CompilerPass {
|
316 | 318 | "JSC_SAME_INTERFACE_MULTIPLE_IMPLEMENTS",
|
317 | 319 | "Cannot @implement the same interface more than once\nRepeated interface: {0}");
|
318 | 320 |
|
| 321 | + static final DiagnosticType PROPERTY_ASSIGNMENT_TO_READONLY_VALUE = |
| 322 | + DiagnosticType.error( |
| 323 | + "JSC_PROPERTY_ASSIGNMENT_TO_READONLY_VALUE", |
| 324 | + "Should not assign to a property of readonly type ''{0}''"); |
| 325 | + |
319 | 326 | // If a diagnostic is disabled by default, do not add it in this list
|
320 | 327 | // TODO(dimvar): Either INEXISTENT_PROPERTY shouldn't be here, or we should
|
321 | 328 | // change DiagnosticGroups.setWarningLevel to not accidentally enable it.
|
322 | 329 | static final DiagnosticGroup ALL_DIAGNOSTICS =
|
323 | 330 | new DiagnosticGroup(
|
324 |
| - DETERMINISTIC_TEST, |
325 |
| - INEXISTENT_ENUM_ELEMENT, |
326 |
| - INEXISTENT_PROPERTY, |
327 |
| - POSSIBLE_INEXISTENT_PROPERTY, |
328 |
| - INEXISTENT_PROPERTY_WITH_SUGGESTION, |
329 |
| - NOT_A_CONSTRUCTOR, |
330 |
| - INSTANTIATE_ABSTRACT_CLASS, |
331 |
| - BIT_OPERATION, |
332 |
| - UNARY_OPERATION, |
| 331 | + ABSTRACT_METHOD_IN_CONCRETE_CLASS, |
| 332 | + ABSTRACT_SUPER_METHOD_NOT_USABLE, |
| 333 | + BAD_IMPLEMENTED_TYPE, |
333 | 334 | BINARY_OPERATION,
|
| 335 | + BIT_OPERATION, |
| 336 | + CONFLICTING_EXTENDED_TYPE, |
334 | 337 | CONFLICTING_GETTER_SETTER_TYPE,
|
335 |
| - NOT_CALLABLE, |
| 338 | + CONFLICTING_IMPLEMENTED_TYPE, |
336 | 339 | CONSTRUCTOR_NOT_CALLABLE,
|
| 340 | + DETERMINISTIC_TEST, |
| 341 | + ES5_CLASS_EXTENDING_ES6_CLASS, |
| 342 | + EXPECTED_THIS_TYPE, |
337 | 343 | FUNCTION_MASKS_VARIABLE,
|
338 |
| - MULTIPLE_VAR_DEF, |
339 |
| - INVALID_INTERFACE_MEMBER_DECLARATION, |
340 |
| - INTERFACE_METHOD_NOT_EMPTY, |
341 |
| - CONFLICTING_EXTENDED_TYPE, |
342 |
| - CONFLICTING_IMPLEMENTED_TYPE, |
343 |
| - BAD_IMPLEMENTED_TYPE, |
344 |
| - TypeValidator.HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, |
345 | 344 | HIDDEN_PROTOTYPAL_SUPERTYPE_PROPERTY_MISMATCH,
|
346 |
| - UNKNOWN_OVERRIDE, |
347 |
| - UNKNOWN_PROTOTYPAL_OVERRIDE, |
348 |
| - WRONG_ARGUMENT_COUNT, |
349 |
| - ILLEGAL_IMPLICIT_CAST, |
350 |
| - INCOMPATIBLE_EXTENDED_PROPERTY_TYPE, |
351 |
| - EXPECTED_THIS_TYPE, |
352 |
| - IN_USED_WITH_STRUCT, |
353 | 345 | ILLEGAL_CLASS_KEY,
|
| 346 | + ILLEGAL_IMPLICIT_CAST, |
| 347 | + ILLEGAL_OBJLIT_KEY, |
354 | 348 | ILLEGAL_PROPERTY_CREATION,
|
355 | 349 | ILLEGAL_PROPERTY_CREATION_ON_UNION_TYPE,
|
356 |
| - ILLEGAL_OBJLIT_KEY, |
| 350 | + INCOMPATIBLE_EXTENDED_PROPERTY_TYPE, |
| 351 | + INEXISTENT_ENUM_ELEMENT, |
| 352 | + INEXISTENT_PROPERTY, |
| 353 | + INEXISTENT_PROPERTY_WITH_SUGGESTION, |
| 354 | + INSTANTIATE_ABSTRACT_CLASS, |
| 355 | + INTERFACE_METHOD_NOT_EMPTY, |
| 356 | + INVALID_INTERFACE_MEMBER_DECLARATION, |
| 357 | + IN_USED_WITH_STRUCT, |
| 358 | + MULTIPLE_VAR_DEF, |
357 | 359 | NON_STRINGIFIABLE_OBJECT_KEY,
|
358 |
| - ABSTRACT_METHOD_IN_CONCRETE_CLASS, |
359 |
| - ABSTRACT_SUPER_METHOD_NOT_USABLE, |
360 |
| - ES5_CLASS_EXTENDING_ES6_CLASS, |
361 |
| - SAME_INTERFACE_MULTIPLE_IMPLEMENTS, |
| 360 | + NOT_A_CONSTRUCTOR, |
| 361 | + NOT_CALLABLE, |
| 362 | + POSSIBLE_INEXISTENT_PROPERTY, |
| 363 | + PROPERTY_ASSIGNMENT_TO_READONLY_VALUE, |
| 364 | + RhinoErrorReporter.CYCLIC_INHERITANCE_ERROR, |
362 | 365 | RhinoErrorReporter.TYPE_PARSE_ERROR,
|
363 | 366 | RhinoErrorReporter.UNRECOGNIZED_TYPE_ERROR,
|
364 |
| - RhinoErrorReporter.CYCLIC_INHERITANCE_ERROR, |
365 |
| - TypedScopeCreator.UNKNOWN_LENDS, |
366 |
| - TypedScopeCreator.LENDS_ON_NON_OBJECT, |
| 367 | + SAME_INTERFACE_MULTIPLE_IMPLEMENTS, |
| 368 | + TypeValidator.HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, |
367 | 369 | TypedScopeCreator.CTOR_INITIALIZER,
|
368 |
| - TypedScopeCreator.IFACE_INITIALIZER); |
| 370 | + TypedScopeCreator.IFACE_INITIALIZER, |
| 371 | + TypedScopeCreator.LENDS_ON_NON_OBJECT, |
| 372 | + TypedScopeCreator.UNKNOWN_LENDS, |
| 373 | + UNARY_OPERATION, |
| 374 | + UNKNOWN_OVERRIDE, |
| 375 | + UNKNOWN_PROTOTYPAL_OVERRIDE, |
| 376 | + WRONG_ARGUMENT_COUNT); |
369 | 377 |
|
370 | 378 | private final AbstractCompiler compiler;
|
371 | 379 | private final TypeValidator validator;
|
@@ -1303,6 +1311,9 @@ private void checkCanAssignToNameGetpropOrGetelem(
|
1303 | 1311 | checkArgument(
|
1304 | 1312 | lvalue.isName() || lvalue.isGetProp() || lvalue.isGetElem() || lvalue.isCast(), lvalue);
|
1305 | 1313 |
|
| 1314 | + // Ensure our LHS is not readonly. |
| 1315 | + checkNotReadonlyPropertyAssignment(lvalue); |
| 1316 | + |
1306 | 1317 | if (lvalue.isGetProp()) {
|
1307 | 1318 | Node object = lvalue.getFirstChild();
|
1308 | 1319 | JSType objectJsType = getJSType(object);
|
@@ -3445,6 +3456,47 @@ private boolean classHasToString(ObjectType type) {
|
3445 | 3456 | return false;
|
3446 | 3457 | }
|
3447 | 3458 |
|
| 3459 | + /** |
| 3460 | + * Given the LHS of a property or element assignment, checks that the type that we're assigning |
| 3461 | + * into is not readonly (ReadonlyArray, in particular). |
| 3462 | + * |
| 3463 | + * <p>This is basically unsound since it checks just "ReadonlyArray" so casting up to "Iterable" |
| 3464 | + * or down to "Array" will defeat it; but it should catch basic errors. |
| 3465 | + */ |
| 3466 | + private void checkNotReadonlyPropertyAssignment(Node lhs) { |
| 3467 | + // We only care about element or property assignments. |
| 3468 | + if (!lhs.isGetProp() && !lhs.isGetElem()) { |
| 3469 | + return; |
| 3470 | + } |
| 3471 | + |
| 3472 | + // If we do not have type information, drop out. |
| 3473 | + JSType lhsType = lhs.getFirstChild().getJSType(); |
| 3474 | + if (lhsType == null) { |
| 3475 | + return; |
| 3476 | + } |
| 3477 | + |
| 3478 | + // We could be a reference to "ReadonlyArray" or a templatized wrapper. |
| 3479 | + JSType roArray = getNativeType(JSTypeNative.READONLY_ARRAY_TYPE); |
| 3480 | + ImmutableList<JSType> alternates = flattenUnion(lhsType); |
| 3481 | + for (int i = 0; i < alternates.size(); i++) { |
| 3482 | + JSType type = alternates.get(i); |
| 3483 | + if (roArray.equals(type) || roArray.equals(maybeReferencedType(type))) { |
| 3484 | + compiler.report(JSError.make(lhs, PROPERTY_ASSIGNMENT_TO_READONLY_VALUE, type.toString())); |
| 3485 | + break; |
| 3486 | + } |
| 3487 | + } |
| 3488 | + } |
| 3489 | + |
| 3490 | + private static @Nullable JSType maybeReferencedType(JSType type) { |
| 3491 | + TemplatizedType maybeTemplatized = type.toMaybeTemplatizedType(); |
| 3492 | + return (maybeTemplatized != null) ? maybeTemplatized.getReferencedType() : null; |
| 3493 | + } |
| 3494 | + |
| 3495 | + private static ImmutableList<JSType> flattenUnion(JSType maybeUnion) { |
| 3496 | + UnionType union = maybeUnion.toMaybeUnionType(); |
| 3497 | + return (union == null) ? ImmutableList.of(maybeUnion) : union.getAlternates(); |
| 3498 | + } |
| 3499 | + |
3448 | 3500 | private static boolean declaresOverride(@Nullable JSDocInfo jsdoc) {
|
3449 | 3501 | return (jsdoc != null) && jsdoc.isOverride();
|
3450 | 3502 | }
|
|
0 commit comments