diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b5306771dd7f7..e25f3681278b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -60,9 +60,9 @@ namespace ts { const emitResolver = createResolver(); - const undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); + const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined"); undefinedSymbol.declarations = []; - const argumentsSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "arguments"); + const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments"); const checker: TypeChecker = { getNodeCount: () => sum(host.getSourceFiles(), "nodeCount"), @@ -131,8 +131,8 @@ namespace ts { const indexedAccessTypes = createMap(); const evolvingArrayTypes: EvolvingArrayType[] = []; - const unknownSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "unknown"); - const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__"); + const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown"); + const resolvingSymbol = createSymbol(0, "__resolving__"); const anyType = createIntrinsicType(TypeFlags.Any, "any"); const autoType = createIntrinsicType(TypeFlags.Any, "any"); @@ -154,7 +154,7 @@ namespace ts { const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral | SymbolFlags.Transient, "__type"); + const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, "__type"); emptyTypeLiteralSymbol.members = createMap(); const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, undefined, undefined); @@ -416,9 +416,11 @@ namespace ts { diagnostics.add(diagnostic); } - function createSymbol(flags: SymbolFlags, name: string): Symbol { + function createSymbol(flags: SymbolFlags, name: string) { symbolCount++; - return new Symbol(flags, name); + const symbol = (new Symbol(flags | SymbolFlags.Transient, name)); + symbol.checkFlags = 0; + return symbol; } function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { @@ -451,7 +453,7 @@ namespace ts { } function cloneSymbol(symbol: Symbol): Symbol { - const result = createSymbol(symbol.flags | SymbolFlags.Merged, symbol.name); + const result = createSymbol(symbol.flags, symbol.name); result.declarations = symbol.declarations.slice(0); result.parent = symbol.parent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; @@ -508,7 +510,7 @@ namespace ts { target.set(id, sourceSymbol); } else { - if (!(targetSymbol.flags & SymbolFlags.Merged)) { + if (!(targetSymbol.flags & SymbolFlags.Transient)) { targetSymbol = cloneSymbol(targetSymbol); target.set(id, targetSymbol); } @@ -545,7 +547,7 @@ namespace ts { if (mainModule.flags & SymbolFlags.Namespace) { // if module symbol has already been merged - it is safe to use it. // otherwise clone it - mainModule = mainModule.flags & SymbolFlags.Merged ? mainModule : cloneSymbol(mainModule); + mainModule = mainModule.flags & SymbolFlags.Transient ? mainModule : cloneSymbol(mainModule); mergeSymbol(mainModule, moduleAugmentation.symbol); } else { @@ -586,6 +588,10 @@ namespace ts { return type.flags & TypeFlags.Object ? (type).objectFlags : 0; } + function getCheckFlags(symbol: Symbol): CheckFlags { + return symbol.flags & SymbolFlags.Transient ? (symbol).checkFlags : 0; + } + function isGlobalSourceFile(node: Node) { return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node); } @@ -594,7 +600,7 @@ namespace ts { if (meaning) { const symbol = symbols.get(name); if (symbol) { - Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (symbol.flags & meaning) { return symbol; } @@ -1426,7 +1432,7 @@ namespace ts { else { Debug.fail("Unknown entity name kind."); } - Debug.assert((symbol.flags & SymbolFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); + Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } @@ -1670,23 +1676,7 @@ namespace ts { } function symbolIsValue(symbol: Symbol): boolean { - // If it is an instantiated symbol, then it is a value if the symbol it is an - // instantiation of is a value. - if (symbol.flags & SymbolFlags.Instantiated) { - return symbolIsValue(getSymbolLinks(symbol).target); - } - - // If the symbol has the value flag, it is trivially a value. - if (symbol.flags & SymbolFlags.Value) { - return true; - } - - // If it is an alias, then it is a value if the symbol it resolves to is a value. - if (symbol.flags & SymbolFlags.Alias) { - return (resolveAlias(symbol).flags & SymbolFlags.Value) !== 0; - } - - return false; + return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value); } function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration { @@ -2238,7 +2228,7 @@ namespace ts { if (parentSymbol) { // Write type arguments of instantiated class/interface here if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { - if (symbol.flags & SymbolFlags.Instantiated) { + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { buildDisplayForTypeArgumentsAndDelimiters(getTypeParametersOfClassOrInterface(parentSymbol), (symbol).mapper, writer, enclosingDeclaration); } @@ -3411,8 +3401,8 @@ namespace ts { } const text = getTextOfPropertyName(name); - const flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0); - const symbol = createSymbol(flags, text); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); symbol.bindingElement = e; members.set(symbol.name, symbol); @@ -3721,7 +3711,7 @@ namespace ts { } function getTypeOfSymbol(symbol: Symbol): Type { - if (symbol.flags & SymbolFlags.Instantiated) { + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { return getTypeOfInstantiatedSymbol(symbol); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { @@ -3746,9 +3736,9 @@ namespace ts { return getObjectFlags(type) & ObjectFlags.Reference ? (type).target : type; } - function hasBaseType(type: BaseType, checkBase: BaseType) { + function hasBaseType(type: Type, checkBase: Type) { return check(type); - function check(type: BaseType): boolean { + function check(type: Type): boolean { if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) { const target = getTargetType(type); return target === checkBase || forEach(getBaseTypes(target), check); @@ -4519,7 +4509,7 @@ namespace ts { s = cloneSignature(signature); if (forEach(unionSignatures, sig => sig.thisParameter)) { const thisType = getUnionType(map(unionSignatures, sig => getTypeOfSymbol(sig.thisParameter) || anyType), /*subtypeReduction*/ true); - s.thisParameter = createTransientSymbol(signature.thisParameter, thisType); + s.thisParameter = createSymbolWithType(signature.thisParameter, thisType); } // Clear resolved return type we possibly got from cloneSignature s.resolvedReturnType = undefined; @@ -4716,9 +4706,9 @@ namespace ts { const propName = (t).text; const modifiersProp = getPropertyOfType(modifiersType, propName); const isOptional = templateOptional || !!(modifiersProp && modifiersProp.flags & SymbolFlags.Optional); - const prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | (isOptional ? SymbolFlags.Optional : 0), propName); + const prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName); + prop.checkFlags = templateReadonly || modifiersProp && isReadonlySymbol(modifiersProp) ? CheckFlags.Readonly : 0; prop.type = propType; - prop.isReadonly = templateReadonly || modifiersProp && isReadonlySymbol(modifiersProp); if (propertySymbol) { prop.mappedTypeOrigin = propertySymbol; } @@ -4949,18 +4939,19 @@ namespace ts { } function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol { - const types = containingType.types; - const excludeModifiers = containingType.flags & TypeFlags.Union ? ModifierFlags.Private | ModifierFlags.Protected : 0; let props: Symbol[]; + const types = containingType.types; + const isUnion = containingType.flags & TypeFlags.Union; + const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; // Flags we want to propagate to the result if they exist in all source symbols - let commonFlags = (containingType.flags & TypeFlags.Intersection) ? SymbolFlags.Optional : SymbolFlags.None; - let isReadonly = false; - let isPartial = false; + let commonFlags = isUnion ? SymbolFlags.None : SymbolFlags.Optional; + let checkFlags = CheckFlags.SyntheticProperty; for (const current of types) { const type = getApparentType(current); if (type !== unknownType) { const prop = getPropertyOfType(type, name); - if (prop && !(getDeclarationModifierFlagsFromSymbol(prop) & excludeModifiers)) { + const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; + if (prop && !(modifiers & excludeModifiers)) { commonFlags &= prop.flags; if (!props) { props = [prop]; @@ -4968,26 +4959,26 @@ namespace ts { else if (!contains(props, prop)) { props.push(prop); } - if (isReadonlySymbol(prop)) { - isReadonly = true; - } - + checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) | + (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | + (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | + (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | + (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); } - else if (containingType.flags & TypeFlags.Union) { - isPartial = true; + else if (isUnion) { + checkFlags |= CheckFlags.Partial; } } } if (!props) { return undefined; } - if (props.length === 1 && !isPartial) { + if (props.length === 1 && !(checkFlags & CheckFlags.Partial)) { return props[0]; } const propTypes: Type[] = []; const declarations: Declaration[] = []; let commonType: Type = undefined; - let hasNonUniformType = false; for (const prop of props) { if (prop.declarations) { addRange(declarations, prop.declarations); @@ -4997,17 +4988,15 @@ namespace ts { commonType = type; } else if (type !== commonType) { - hasNonUniformType = true; + checkFlags |= CheckFlags.HasNonUniformType; } propTypes.push(type); } - const result = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | SymbolFlags.SyntheticProperty | commonFlags, name); + const result = createSymbol(SymbolFlags.Property | commonFlags, name); + result.checkFlags = checkFlags; result.containingType = containingType; - result.hasNonUniformType = hasNonUniformType; - result.isPartial = isPartial; result.declarations = declarations; - result.isReadonly = isReadonly; - result.type = containingType.flags & TypeFlags.Union ? getUnionType(propTypes) : getIntersectionType(propTypes); + result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); return result; } @@ -5031,7 +5020,7 @@ namespace ts { function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: string): Symbol { const property = getUnionOrIntersectionProperty(type, name); // We need to filter out partial properties in union types - return property && !(property.flags & SymbolFlags.SyntheticProperty && (property).isPartial) ? property : undefined; + return property && !(getCheckFlags(property) & CheckFlags.Partial) ? property : undefined; } /** @@ -5835,7 +5824,7 @@ namespace ts { for (let i = 0; i < arity; i++) { const typeParameter = createType(TypeFlags.TypeParameter); typeParameters.push(typeParameter); - const property = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); + const property = createSymbol(SymbolFlags.Property, "" + i); property.type = typeParameter; properties.push(property); } @@ -6081,7 +6070,10 @@ namespace ts { if (type.flags & TypeFlags.Union && typeSet.unionIndex === undefined) { typeSet.unionIndex = typeSet.length; } - typeSet.push(type); + if (!(type.flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous && + type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) { + typeSet.push(type); + } } } @@ -6419,13 +6411,13 @@ namespace ts { const rightType = getTypeOfSymbol(rightProp); if (maybeTypeOfKind(rightType, TypeFlags.Undefined) || rightProp.flags & SymbolFlags.Optional) { const declarations: Declaration[] = concatenate(leftProp.declarations, rightProp.declarations); - const flags = SymbolFlags.Property | SymbolFlags.Transient | (leftProp.flags & SymbolFlags.Optional); - const result = createSymbol(flags, leftProp.name); + const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); + const result = createSymbol(flags, leftProp.name); + result.checkFlags = isReadonlySymbol(leftProp) || isReadonlySymbol(rightProp) ? CheckFlags.Readonly : 0; result.type = getUnionType([getTypeOfSymbol(leftProp), getTypeWithFacts(rightType, TypeFacts.NEUndefined)]); result.leftSpread = leftProp; result.rightSpread = rightProp; result.declarations = declarations; - result.isReadonly = isReadonlySymbol(leftProp) || isReadonlySymbol(rightProp); members.set(leftProp.name, result); } } @@ -6752,7 +6744,7 @@ namespace ts { } function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { - if (symbol.flags & SymbolFlags.Instantiated) { + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { const links = getSymbolLinks(symbol); // If symbol being instantiated is itself a instantiation, fetch the original target and combine the // type mappers. This ensures that original type identities are properly preserved and that aliases @@ -6760,10 +6752,10 @@ namespace ts { symbol = links.target; mapper = combineTypeMappers(links.mapper, mapper); } - // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and // also transient so that we can just store data on it directly. - const result = createSymbol(SymbolFlags.Instantiated | SymbolFlags.Transient | symbol.flags, symbol.name); + const result = createSymbol(symbol.flags, symbol.name); + result.checkFlags = CheckFlags.Instantiated; result.declarations = symbol.declarations; result.parent = symbol.parent; result.target = symbol; @@ -6771,7 +6763,6 @@ namespace ts { if (symbol.valueDeclaration) { result.valueDeclaration = symbol.valueDeclaration; } - return result; } @@ -7966,6 +7957,12 @@ namespace ts { const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { + if (getCheckFlags(sourceProp) & CheckFlags.ContainsPrivate) { + if (reportErrors) { + reportError(Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(sourceProp), typeToString(source)); + } + return Ternary.False; + } if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { if (reportErrors) { if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { @@ -7981,13 +7978,10 @@ namespace ts { } } else if (targetPropFlags & ModifierFlags.Protected) { - const sourceDeclaredInClass = sourceProp.parent && sourceProp.parent.flags & SymbolFlags.Class; - const sourceClass = sourceDeclaredInClass ? getDeclaredTypeOfSymbol(getParentOfSymbol(sourceProp)) : undefined; - const targetClass = getDeclaredTypeOfSymbol(getParentOfSymbol(targetProp)); - if (!sourceClass || !hasBaseType(sourceClass, targetClass)) { + if (!isValidOverrideOf(sourceProp, targetProp)) { if (reportErrors) { - reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, - symbolToString(targetProp), typeToString(sourceClass || source), typeToString(targetClass)); + reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), + typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); } return Ternary.False; } @@ -8229,6 +8223,49 @@ namespace ts { } } + // Invoke the callback for each underlying property symbol of the given symbol and return the first + // value that isn't undefined. + function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T { + if (getCheckFlags(prop) & CheckFlags.SyntheticProperty) { + for (const t of (prop).containingType.types) { + const p = getPropertyOfType(t, prop.name); + const result = p && forEachProperty(p, callback); + if (result) { + return result; + } + } + return undefined; + } + return callback(prop); + } + + // Return the declaring class type of a property or undefined if property not declared in class + function getDeclaringClass(prop: Symbol) { + return prop.parent && prop.parent.flags & SymbolFlags.Class ? getDeclaredTypeOfSymbol(getParentOfSymbol(prop)) : undefined; + } + + // Return true if some underlying source property is declared in a class that derives + // from the given base class. + function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type) { + return forEachProperty(prop, sp => { + const sourceClass = getDeclaringClass(sp); + return sourceClass ? hasBaseType(sourceClass, baseClass) : false; + }); + } + + // Return true if source property is a valid override of protected parts of target property. + function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { + return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? + !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); + } + + // Return true if the given class derives from each of the declaring classes of the protected + // constituents of the given property. + function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol) { + return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.Protected ? + !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; + } + // Return true if the given type is the constructor type for an abstract class function isAbstractConstructorType(type: Type) { if (getObjectFlags(type) & ObjectFlags.Anonymous) { @@ -8561,8 +8598,8 @@ namespace ts { getSignaturesOfType(type, SignatureKind.Construct).length === 0; } - function createTransientSymbol(source: Symbol, type: Type) { - const symbol = createSymbol(source.flags | SymbolFlags.Transient, source.name); + function createSymbolWithType(source: Symbol, type: Type) { + const symbol = createSymbol(source.flags, source.name); symbol.declarations = source.declarations; symbol.parent = source.parent; symbol.type = type; @@ -8578,7 +8615,7 @@ namespace ts { for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); - members.set(property.name, updated === original ? property : createTransientSymbol(property, updated)); + members.set(property.name, updated === original ? property : createSymbolWithType(property, updated)); }; return members; } @@ -8816,10 +8853,10 @@ namespace ts { if (!inferredPropType) { return undefined; } - const inferredProp = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & optionalMask, prop.name); + const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.name); + inferredProp.checkFlags = readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0; inferredProp.declarations = prop.declarations; inferredProp.type = inferredPropType; - inferredProp.isReadonly = readonlyMask && isReadonlySymbol(prop); members.set(prop.name, inferredProp); } if (indexInfo) { @@ -9301,9 +9338,9 @@ namespace ts { function isDiscriminantProperty(type: Type, name: string) { if (type && type.flags & TypeFlags.Union) { const prop = getUnionOrIntersectionProperty(type, name); - if (prop && prop.flags & SymbolFlags.SyntheticProperty) { + if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { - (prop).isDiscriminantProperty = (prop).hasNonUniformType && isLiteralType(getTypeOfSymbol(prop)); + (prop).isDiscriminantProperty = (prop).checkFlags & CheckFlags.HasNonUniformType && isLiteralType(getTypeOfSymbol(prop)); } return (prop).isDiscriminantProperty; } @@ -11760,7 +11797,7 @@ namespace ts { } typeFlags |= type.flags; - const prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.name); + const prop = createSymbol(SymbolFlags.Property | member.flags, member.name); if (inDestructuringPattern) { // If object literal is an assignment pattern and if the assignment pattern specifies a default value // for the property, make the property optional. @@ -12385,7 +12422,22 @@ namespace ts { } function getDeclarationModifierFlagsFromSymbol(s: Symbol): ModifierFlags { - return s.valueDeclaration ? getCombinedModifierFlags(s.valueDeclaration) : s.flags & SymbolFlags.Prototype ? ModifierFlags.Public | ModifierFlags.Static : 0; + if (s.valueDeclaration) { + const flags = getCombinedModifierFlags(s.valueDeclaration); + return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; + } + if (getCheckFlags(s) & CheckFlags.SyntheticProperty) { + const checkFlags = (s).checkFlags; + const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : + checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : + ModifierFlags.Protected; + const staticModifier = checkFlags & CheckFlags.ContainsStatic ? ModifierFlags.Static : 0; + return accessModifier | staticModifier; + } + if (s.flags & SymbolFlags.Prototype) { + return ModifierFlags.Public | ModifierFlags.Static; + } + return 0; } function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { @@ -12400,12 +12452,18 @@ namespace ts { * @param type The type of left. * @param prop The symbol for the right hand side of the property access. */ - function checkClassPropertyAccess(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration, left: Expression | QualifiedName, type: Type, prop: Symbol): boolean { + function checkPropertyAccessibility(node: PropertyAccessExpression | QualifiedName | VariableLikeDeclaration, left: Expression | QualifiedName, type: Type, prop: Symbol): boolean { const flags = getDeclarationModifierFlagsFromSymbol(prop); - const declaringClass = getDeclaredTypeOfSymbol(getParentOfSymbol(prop)); const errorNode = node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.VariableDeclaration ? (node).name : (node).right; + + if (getCheckFlags(prop) & CheckFlags.ContainsPrivate) { + // Synthetic property with private constituent property + error(errorNode, Diagnostics.Property_0_has_conflicting_declarations_and_is_inaccessible_in_type_1, symbolToString(prop), typeToString(type)); + return false; + } + if (left.kind === SyntaxKind.SuperKeyword) { // TS 1.0 spec (April 2014): 4.8.2 // - In a constructor, instance member function, instance member accessor, or @@ -12424,14 +12482,12 @@ namespace ts { return false; } } - if (flags & ModifierFlags.Abstract) { // A method cannot be accessed in a super property access if the method is abstract. // This error could mask a private property access error. But, a member // cannot simultaneously be private and abstract, so this will trigger an // additional error elsewhere. - - error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(declaringClass)); + error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop))); return false; } } @@ -12447,7 +12503,7 @@ namespace ts { if (flags & ModifierFlags.Private) { const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)); if (!isNodeWithinClass(node, declaringClassDeclaration)) { - error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(declaringClass)); + error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop))); return false; } return true; @@ -12460,15 +12516,15 @@ namespace ts { return true; } - // Get the enclosing class that has the declaring class as its base type + // Find the first enclosing class that has the declaring classes of the protected constituents + // of the property as base classes const enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => { const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)); - return hasBaseType(enclosingClass, declaringClass) ? enclosingClass : undefined; + return isClassDerivedFromDeclaringClasses(enclosingClass, prop) ? enclosingClass : undefined; }); - // A protected property is accessible if the property is within the declaring class or classes derived from it if (!enclosingClass) { - error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(declaringClass)); + error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type)); return false; } // No further restrictions for static properties @@ -12480,9 +12536,7 @@ namespace ts { // get the original type -- represented as the type constraint of the 'this' type type = getConstraintOfTypeParameter(type); } - - // TODO: why is the first part of this check here? - if (!(getObjectFlags(getTargetType(type)) & ObjectFlags.ClassOrInterface && hasBaseType(type, enclosingClass))) { + if (!(getObjectFlags(getTargetType(type)) & ObjectFlags.ClassOrInterface && hasBaseType(type, enclosingClass))) { error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1, symbolToString(prop), typeToString(enclosingClass)); return false; } @@ -12533,9 +12587,8 @@ namespace ts { noUnusedIdentifiers && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration && (getModifierFlags(prop.valueDeclaration) & ModifierFlags.Private)) { - if (prop.flags & SymbolFlags.Instantiated) { + if (getCheckFlags(prop) & CheckFlags.Instantiated) { getSymbolLinks(prop).target.isReferenced = true; - } else { prop.isReferenced = true; @@ -12570,9 +12623,7 @@ namespace ts { getNodeLinks(node).resolvedSymbol = prop; - if (prop.parent && prop.parent.flags & SymbolFlags.Class) { - checkClassPropertyAccess(node, left, apparentType, prop); - } + checkPropertyAccessibility(node, left, apparentType, prop); const propType = getTypeOfSymbol(prop); const assignmentKind = getAssignmentTargetKind(node); @@ -12604,8 +12655,8 @@ namespace ts { const type = checkExpression(left); if (type !== unknownType && !isTypeAny(type)) { const prop = getPropertyOfType(getWidenedType(type), propertyName); - if (prop && prop.parent && prop.parent.flags & SymbolFlags.Class) { - return checkClassPropertyAccess(node, left, type, prop); + if (prop) { + return checkPropertyAccessibility(node, left, type, prop); } } return true; @@ -14099,7 +14150,7 @@ namespace ts { const parameter = signature.thisParameter; if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration).type) { if (!parameter) { - signature.thisParameter = createTransientSymbol(context.thisParameter, undefined); + signature.thisParameter = createSymbolWithType(context.thisParameter, undefined); } assignTypeToParameterAndFixTypeParameters(signature.thisParameter, getTypeOfSymbol(context.thisParameter), mapper); } @@ -14541,11 +14592,11 @@ namespace ts { // Get accessors without matching set accessors // Enum members // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) - return symbol.isReadonly || - symbol.flags & SymbolFlags.Property && (getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly) !== 0 || - symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 || + return !!(getCheckFlags(symbol) & CheckFlags.Readonly || + symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || + symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || - (symbol.flags & SymbolFlags.EnumMember) !== 0; + symbol.flags & SymbolFlags.EnumMember); } function isReferenceToReadonlyEntity(expr: Expression, symbol: Symbol): boolean { @@ -17544,8 +17595,8 @@ namespace ts { const name = node.propertyName || node.name; const property = getPropertyOfType(parentType, getTextOfPropertyName(name)); markPropertyAsReferenced(property); - if (parent.initializer && property && getParentOfSymbol(property)) { - checkClassPropertyAccess(parent, parent.initializer, parentType, property); + if (parent.initializer && property) { + checkPropertyAccessibility(parent, parent.initializer, parentType, property); } } @@ -18551,7 +18602,7 @@ namespace ts { function getTargetSymbol(s: Symbol) { // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags - return s.flags & SymbolFlags.Instantiated ? getSymbolLinks(s).target : s; + return getCheckFlags(s) & CheckFlags.Instantiated ? (s).target : s; } function getClassLikeDeclarationOfSymbol(symbol: Symbol): Declaration { @@ -19129,7 +19180,7 @@ namespace ts { // We can detect if augmentation was applied using following rules: // - augmentation for a global scope is always applied // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). - const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Merged); + const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); if (checkBody && node.body) { // body of ambient external module is always a module block for (const statement of (node.body).statements) { @@ -19208,7 +19259,7 @@ namespace ts { // this is done it two steps // 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error // 2. main check - report error if value declaration of the parent symbol is module augmentation) - let reportError = !(symbol.flags & SymbolFlags.Merged); + let reportError = !(symbol.flags & SymbolFlags.Transient); if (!reportError) { // symbol should not originate in augmentation reportError = isExternalModuleAugmentation(symbol.parent.declarations[0]); @@ -20310,7 +20361,7 @@ namespace ts { } function getRootSymbols(symbol: Symbol): Symbol[] { - if (symbol.flags & SymbolFlags.SyntheticProperty) { + if (getCheckFlags(symbol) & CheckFlags.SyntheticProperty) { const symbols: Symbol[] = []; const name = symbol.name; forEach(getSymbolLinks(symbol).containingType.types, t => { @@ -21061,7 +21112,7 @@ namespace ts { function createThenableType() { // build the thenable type that is used to verify against a non-promise "thenable" operand to `await`. - const thenPropertySymbol = createSymbol(SymbolFlags.Transient | SymbolFlags.Property, "then"); + const thenPropertySymbol = createSymbol(SymbolFlags.Property, "then"); getSymbolLinks(thenPropertySymbol).type = globalFunctionType; const thenableType = createObjectType(ObjectFlags.Anonymous); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 93953a3b90a7b..880547bac8520 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1787,6 +1787,10 @@ "category": "Error", "code": 2545 }, + "Property '{0}' has conflicting declarations and is inaccessible in type '{1}'.": { + "category": "Error", + "code": 2546 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8660e6138cdad..db9873734aa7e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2600,37 +2600,34 @@ export const enum SymbolFlags { None = 0, - FunctionScopedVariable = 0x00000001, // Variable (var) or parameter - BlockScopedVariable = 0x00000002, // A block-scoped variable (let or const) - Property = 0x00000004, // Property or enum member - EnumMember = 0x00000008, // Enum member - Function = 0x00000010, // Function - Class = 0x00000020, // Class - Interface = 0x00000040, // Interface - ConstEnum = 0x00000080, // Const enum - RegularEnum = 0x00000100, // Enum - ValueModule = 0x00000200, // Instantiated module - NamespaceModule = 0x00000400, // Uninstantiated module - TypeLiteral = 0x00000800, // Type Literal or mapped type - ObjectLiteral = 0x00001000, // Object Literal - Method = 0x00002000, // Method - Constructor = 0x00004000, // Constructor - GetAccessor = 0x00008000, // Get accessor - SetAccessor = 0x00010000, // Set accessor - Signature = 0x00020000, // Call, construct, or index signature - TypeParameter = 0x00040000, // Type parameter - TypeAlias = 0x00080000, // Type alias - ExportValue = 0x00100000, // Exported value marker (see comment in declareModuleMember in binder) - ExportType = 0x00200000, // Exported type marker (see comment in declareModuleMember in binder) - ExportNamespace = 0x00400000, // Exported namespace marker (see comment in declareModuleMember in binder) - Alias = 0x00800000, // An alias for another symbol (see comment in isAliasSymbolDeclaration in checker) - Instantiated = 0x01000000, // Instantiated symbol - Merged = 0x02000000, // Merged symbol (created during program binding) - Transient = 0x04000000, // Transient symbol (created during type check) - Prototype = 0x08000000, // Prototype property (no source representation) - SyntheticProperty = 0x10000000, // Property in union or intersection type - Optional = 0x20000000, // Optional property - ExportStar = 0x40000000, // Export * declaration + FunctionScopedVariable = 1 << 0, // Variable (var) or parameter + BlockScopedVariable = 1 << 1, // A block-scoped variable (let or const) + Property = 1 << 2, // Property or enum member + EnumMember = 1 << 3, // Enum member + Function = 1 << 4, // Function + Class = 1 << 5, // Class + Interface = 1 << 6, // Interface + ConstEnum = 1 << 7, // Const enum + RegularEnum = 1 << 8, // Enum + ValueModule = 1 << 9, // Instantiated module + NamespaceModule = 1 << 10, // Uninstantiated module + TypeLiteral = 1 << 11, // Type Literal or mapped type + ObjectLiteral = 1 << 12, // Object Literal + Method = 1 << 13, // Method + Constructor = 1 << 14, // Constructor + GetAccessor = 1 << 15, // Get accessor + SetAccessor = 1 << 16, // Set accessor + Signature = 1 << 17, // Call, construct, or index signature + TypeParameter = 1 << 18, // Type parameter + TypeAlias = 1 << 19, // Type alias + ExportValue = 1 << 20, // Exported value marker (see comment in declareModuleMember in binder) + ExportType = 1 << 21, // Exported type marker (see comment in declareModuleMember in binder) + ExportNamespace = 1 << 22, // Exported namespace marker (see comment in declareModuleMember in binder) + Alias = 1 << 23, // An alias for another symbol (see comment in isAliasSymbolDeclaration in checker) + Prototype = 1 << 24, // Prototype property (no source representation) + ExportStar = 1 << 25, // Export * declaration + Optional = 1 << 26, // Optional property + Transient = 1 << 27, // Transient symbol (created during type check) Enum = RegularEnum | ConstEnum, Variable = FunctionScopedVariable | BlockScopedVariable, @@ -2690,11 +2687,9 @@ name: string; // Name of symbol declarations?: Declaration[]; // Declarations associated with this symbol valueDeclaration?: Declaration; // First value declaration of the symbol - members?: SymbolTable; // Class, interface or literal instance members exports?: SymbolTable; // Module exports globalExports?: SymbolTable; // Conditional global UMD exports - /* @internal */ isReadonly?: boolean; // readonly? (set only for intersections and unions) /* @internal */ id?: number; // Unique id (used to look up SymbolLinks) /* @internal */ mergeId?: number; // Merge id (used to look up merged symbol) /* @internal */ parent?: Symbol; // Parent symbol @@ -2719,8 +2714,6 @@ leftSpread?: Symbol; // Left source for synthetic spread property rightSpread?: Symbol; // Right source for synthetic spread property mappedTypeOrigin?: Symbol; // For a property on a mapped type, points back to the orignal 'T' from 'keyof T'. - hasNonUniformType?: boolean; // True if constituents have non-uniform types - isPartial?: boolean; // True if syntheric property of union type occurs in some but not all constituents isDiscriminantProperty?: boolean; // True if discriminant synthetic property resolvedExports?: SymbolTable; // Resolved exports of module exportsChecked?: boolean; // True if exports of external module have been checked @@ -2730,7 +2723,22 @@ } /* @internal */ - export interface TransientSymbol extends Symbol, SymbolLinks { } + export const enum CheckFlags { + Instantiated = 1 << 0, // Instantiated symbol + SyntheticProperty = 1 << 1, // Property in union or intersection type + Readonly = 1 << 2, // Readonly transient symbol + Partial = 1 << 3, // Synthetic property present in some but not all constituents + HasNonUniformType = 1 << 4, // Synthetic property with non-uniform type in constituents + ContainsPublic = 1 << 5, // Synthetic property with public constituent(s) + ContainsProtected = 1 << 6, // Synthetic property with protected constituent(s) + ContainsPrivate = 1 << 7, // Synthetic property with private constituent(s) + ContainsStatic = 1 << 8, // Synthetic property with static constituent(s) + } + + /* @internal */ + export interface TransientSymbol extends Symbol, SymbolLinks { + checkFlags: CheckFlags; + } export type SymbolTable = Map; diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 28474839cf3c9..cbdaff830ce26 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -277,7 +277,7 @@ namespace ts.FindAllReferences { // if this symbol is visible from its parent container, e.g. exported, then bail out // if symbol correspond to the union property - bail out - if (symbol.parent || (symbol.flags & SymbolFlags.SyntheticProperty)) { + if (symbol.parent || (symbol.flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty)) { return undefined; } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index b32bd8f331d4f..58c83de850993 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -51,7 +51,7 @@ namespace ts.SymbolDisplay { if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; if (flags & SymbolFlags.Property) { - if (flags & SymbolFlags.SyntheticProperty) { + if (flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty) { // If union property is result of union of non method (property/accessors/variables), it is labeled as property const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { const rootSymbolFlags = rootSymbol.getFlags(); diff --git a/tests/baselines/reference/mixinAccessModifiers.errors.txt b/tests/baselines/reference/mixinAccessModifiers.errors.txt new file mode 100644 index 0000000000000..a161a8558812f --- /dev/null +++ b/tests/baselines/reference/mixinAccessModifiers.errors.txt @@ -0,0 +1,156 @@ +tests/cases/conformance/classes/mixinAccessModifiers.ts(39,4): error TS2546: Property 'p' has conflicting declarations and is inaccessible in type 'Private & Private2'. +tests/cases/conformance/classes/mixinAccessModifiers.ts(43,4): error TS2546: Property 'p' has conflicting declarations and is inaccessible in type 'Private & Protected'. +tests/cases/conformance/classes/mixinAccessModifiers.ts(47,4): error TS2546: Property 'p' has conflicting declarations and is inaccessible in type 'Private & Public'. +tests/cases/conformance/classes/mixinAccessModifiers.ts(51,4): error TS2445: Property 'p' is protected and only accessible within class 'Protected & Protected2' and its subclasses. +tests/cases/conformance/classes/mixinAccessModifiers.ts(66,7): error TS2415: Class 'C1' incorrectly extends base class 'Private & Private2'. + Type 'C1' is not assignable to type 'Private'. + Property 'p' has conflicting declarations and is inaccessible in type 'C1'. +tests/cases/conformance/classes/mixinAccessModifiers.ts(67,7): error TS2415: Class 'C2' incorrectly extends base class 'Private & Protected'. + Type 'C2' is not assignable to type 'Private'. + Property 'p' has conflicting declarations and is inaccessible in type 'C2'. +tests/cases/conformance/classes/mixinAccessModifiers.ts(68,7): error TS2415: Class 'C3' incorrectly extends base class 'Private & Public'. + Type 'C3' is not assignable to type 'Private'. + Property 'p' has conflicting declarations and is inaccessible in type 'C3'. +tests/cases/conformance/classes/mixinAccessModifiers.ts(85,6): error TS2445: Property 'p' is protected and only accessible within class 'C4' and its subclasses. +tests/cases/conformance/classes/mixinAccessModifiers.ts(90,6): error TS2445: Property 's' is protected and only accessible within class 'typeof C4' and its subclasses. +tests/cases/conformance/classes/mixinAccessModifiers.ts(98,6): error TS2445: Property 'p' is protected and only accessible within class 'C4' and its subclasses. +tests/cases/conformance/classes/mixinAccessModifiers.ts(103,6): error TS2445: Property 's' is protected and only accessible within class 'typeof C4' and its subclasses. + + +==== tests/cases/conformance/classes/mixinAccessModifiers.ts (11 errors) ==== + + type Constructable = new (...args: any[]) => object; + + class Private { + constructor (...args: any[]) {} + private p: string; + } + + class Private2 { + constructor (...args: any[]) {} + private p: string; + } + + class Protected { + constructor (...args: any[]) {} + protected p: string; + protected static s: string; + } + + class Protected2 { + constructor (...args: any[]) {} + protected p: string; + protected static s: string; + } + + class Public { + constructor (...args: any[]) {} + public p: string; + public static s: string; + } + + class Public2 { + constructor (...args: any[]) {} + public p: string; + public static s: string; + } + + function f1(x: Private & Private2) { + x.p; // Error, private constituent makes property inaccessible + ~ +!!! error TS2546: Property 'p' has conflicting declarations and is inaccessible in type 'Private & Private2'. + } + + function f2(x: Private & Protected) { + x.p; // Error, private constituent makes property inaccessible + ~ +!!! error TS2546: Property 'p' has conflicting declarations and is inaccessible in type 'Private & Protected'. + } + + function f3(x: Private & Public) { + x.p; // Error, private constituent makes property inaccessible + ~ +!!! error TS2546: Property 'p' has conflicting declarations and is inaccessible in type 'Private & Public'. + } + + function f4(x: Protected & Protected2) { + x.p; // Error, protected when all constituents are protected + ~ +!!! error TS2445: Property 'p' is protected and only accessible within class 'Protected & Protected2' and its subclasses. + } + + function f5(x: Protected & Public) { + x.p; // Ok, public if any constituent is public + } + + function f6(x: Public & Public2) { + x.p; // Ok, public if any constituent is public + } + + declare function Mix(c1: T, c2: U): T & U; + + // Can't derive from type with inaccessible properties + + class C1 extends Mix(Private, Private2) {} + ~~ +!!! error TS2415: Class 'C1' incorrectly extends base class 'Private & Private2'. +!!! error TS2415: Type 'C1' is not assignable to type 'Private'. +!!! error TS2415: Property 'p' has conflicting declarations and is inaccessible in type 'C1'. + class C2 extends Mix(Private, Protected) {} + ~~ +!!! error TS2415: Class 'C2' incorrectly extends base class 'Private & Protected'. +!!! error TS2415: Type 'C2' is not assignable to type 'Private'. +!!! error TS2415: Property 'p' has conflicting declarations and is inaccessible in type 'C2'. + class C3 extends Mix(Private, Public) {} + ~~ +!!! error TS2415: Class 'C3' incorrectly extends base class 'Private & Public'. +!!! error TS2415: Type 'C3' is not assignable to type 'Private'. +!!! error TS2415: Property 'p' has conflicting declarations and is inaccessible in type 'C3'. + + class C4 extends Mix(Protected, Protected2) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; + c5.p; + c6.p; + } + static g() { + C4.s; + C5.s; + C6.s + } + } + + class C5 extends Mix(Protected, Public) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; // Error, not in class deriving from Protected2 + ~ +!!! error TS2445: Property 'p' is protected and only accessible within class 'C4' and its subclasses. + c5.p; + c6.p; + } + static g() { + C4.s; // Error, not in class deriving from Protected2 + ~ +!!! error TS2445: Property 's' is protected and only accessible within class 'typeof C4' and its subclasses. + C5.s; + C6.s + } + } + + class C6 extends Mix(Public, Public2) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; // Error, not in class deriving from Protected2 + ~ +!!! error TS2445: Property 'p' is protected and only accessible within class 'C4' and its subclasses. + c5.p; + c6.p; + } + static g() { + C4.s; // Error, not in class deriving from Protected2 + ~ +!!! error TS2445: Property 's' is protected and only accessible within class 'typeof C4' and its subclasses. + C5.s; + C6.s + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/mixinAccessModifiers.js b/tests/baselines/reference/mixinAccessModifiers.js new file mode 100644 index 0000000000000..5bf42f480fb29 --- /dev/null +++ b/tests/baselines/reference/mixinAccessModifiers.js @@ -0,0 +1,323 @@ +//// [mixinAccessModifiers.ts] + +type Constructable = new (...args: any[]) => object; + +class Private { + constructor (...args: any[]) {} + private p: string; +} + +class Private2 { + constructor (...args: any[]) {} + private p: string; +} + +class Protected { + constructor (...args: any[]) {} + protected p: string; + protected static s: string; +} + +class Protected2 { + constructor (...args: any[]) {} + protected p: string; + protected static s: string; +} + +class Public { + constructor (...args: any[]) {} + public p: string; + public static s: string; +} + +class Public2 { + constructor (...args: any[]) {} + public p: string; + public static s: string; +} + +function f1(x: Private & Private2) { + x.p; // Error, private constituent makes property inaccessible +} + +function f2(x: Private & Protected) { + x.p; // Error, private constituent makes property inaccessible +} + +function f3(x: Private & Public) { + x.p; // Error, private constituent makes property inaccessible +} + +function f4(x: Protected & Protected2) { + x.p; // Error, protected when all constituents are protected +} + +function f5(x: Protected & Public) { + x.p; // Ok, public if any constituent is public +} + +function f6(x: Public & Public2) { + x.p; // Ok, public if any constituent is public +} + +declare function Mix(c1: T, c2: U): T & U; + +// Can't derive from type with inaccessible properties + +class C1 extends Mix(Private, Private2) {} +class C2 extends Mix(Private, Protected) {} +class C3 extends Mix(Private, Public) {} + +class C4 extends Mix(Protected, Protected2) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; + c5.p; + c6.p; + } + static g() { + C4.s; + C5.s; + C6.s + } +} + +class C5 extends Mix(Protected, Public) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; // Error, not in class deriving from Protected2 + c5.p; + c6.p; + } + static g() { + C4.s; // Error, not in class deriving from Protected2 + C5.s; + C6.s + } +} + +class C6 extends Mix(Public, Public2) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; // Error, not in class deriving from Protected2 + c5.p; + c6.p; + } + static g() { + C4.s; // Error, not in class deriving from Protected2 + C5.s; + C6.s + } +} + + +//// [mixinAccessModifiers.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var Private = (function () { + function Private() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + } + return Private; +}()); +var Private2 = (function () { + function Private2() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + } + return Private2; +}()); +var Protected = (function () { + function Protected() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + } + return Protected; +}()); +var Protected2 = (function () { + function Protected2() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + } + return Protected2; +}()); +var Public = (function () { + function Public() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + } + return Public; +}()); +var Public2 = (function () { + function Public2() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + } + return Public2; +}()); +function f1(x) { + x.p; // Error, private constituent makes property inaccessible +} +function f2(x) { + x.p; // Error, private constituent makes property inaccessible +} +function f3(x) { + x.p; // Error, private constituent makes property inaccessible +} +function f4(x) { + x.p; // Error, protected when all constituents are protected +} +function f5(x) { + x.p; // Ok, public if any constituent is public +} +function f6(x) { + x.p; // Ok, public if any constituent is public +} +// Can't derive from type with inaccessible properties +var C1 = (function (_super) { + __extends(C1, _super); + function C1() { + return _super !== null && _super.apply(this, arguments) || this; + } + return C1; +}(Mix(Private, Private2))); +var C2 = (function (_super) { + __extends(C2, _super); + function C2() { + return _super !== null && _super.apply(this, arguments) || this; + } + return C2; +}(Mix(Private, Protected))); +var C3 = (function (_super) { + __extends(C3, _super); + function C3() { + return _super !== null && _super.apply(this, arguments) || this; + } + return C3; +}(Mix(Private, Public))); +var C4 = (function (_super) { + __extends(C4, _super); + function C4() { + return _super !== null && _super.apply(this, arguments) || this; + } + C4.prototype.f = function (c4, c5, c6) { + c4.p; + c5.p; + c6.p; + }; + C4.g = function () { + C4.s; + C5.s; + C6.s; + }; + return C4; +}(Mix(Protected, Protected2))); +var C5 = (function (_super) { + __extends(C5, _super); + function C5() { + return _super !== null && _super.apply(this, arguments) || this; + } + C5.prototype.f = function (c4, c5, c6) { + c4.p; // Error, not in class deriving from Protected2 + c5.p; + c6.p; + }; + C5.g = function () { + C4.s; // Error, not in class deriving from Protected2 + C5.s; + C6.s; + }; + return C5; +}(Mix(Protected, Public))); +var C6 = (function (_super) { + __extends(C6, _super); + function C6() { + return _super !== null && _super.apply(this, arguments) || this; + } + C6.prototype.f = function (c4, c5, c6) { + c4.p; // Error, not in class deriving from Protected2 + c5.p; + c6.p; + }; + C6.g = function () { + C4.s; // Error, not in class deriving from Protected2 + C5.s; + C6.s; + }; + return C6; +}(Mix(Public, Public2))); + + +//// [mixinAccessModifiers.d.ts] +declare type Constructable = new (...args: any[]) => object; +declare class Private { + constructor(...args: any[]); + private p; +} +declare class Private2 { + constructor(...args: any[]); + private p; +} +declare class Protected { + constructor(...args: any[]); + protected p: string; + protected static s: string; +} +declare class Protected2 { + constructor(...args: any[]); + protected p: string; + protected static s: string; +} +declare class Public { + constructor(...args: any[]); + p: string; + static s: string; +} +declare class Public2 { + constructor(...args: any[]); + p: string; + static s: string; +} +declare function f1(x: Private & Private2): void; +declare function f2(x: Private & Protected): void; +declare function f3(x: Private & Public): void; +declare function f4(x: Protected & Protected2): void; +declare function f5(x: Protected & Public): void; +declare function f6(x: Public & Public2): void; +declare function Mix(c1: T, c2: U): T & U; +declare class C1 extends Private & Private2 { +} +declare class C2 extends Private & Protected { +} +declare class C3 extends Private & Public { +} +declare class C4 extends Protected & Protected2 { + f(c4: C4, c5: C5, c6: C6): void; + static g(): void; +} +declare class C5 extends Protected & Public { + f(c4: C4, c5: C5, c6: C6): void; + static g(): void; +} +declare class C6 extends Public & Public2 { + f(c4: C4, c5: C5, c6: C6): void; + static g(): void; +} diff --git a/tests/baselines/reference/mixinPrivateAndProtected.errors.txt b/tests/baselines/reference/mixinPrivateAndProtected.errors.txt new file mode 100644 index 0000000000000..131aff967d9b4 --- /dev/null +++ b/tests/baselines/reference/mixinPrivateAndProtected.errors.txt @@ -0,0 +1,116 @@ +tests/cases/compiler/mixinPrivateAndProtected.ts(46,3): error TS2445: Property 'ptd' is protected and only accessible within class 'A' and its subclasses. +tests/cases/compiler/mixinPrivateAndProtected.ts(47,3): error TS2341: Property 'pvt' is private and only accessible within class 'A'. +tests/cases/compiler/mixinPrivateAndProtected.ts(50,4): error TS2445: Property 'ptd' is protected and only accessible within class 'mixB.(Anonymous class) & A' and its subclasses. +tests/cases/compiler/mixinPrivateAndProtected.ts(51,4): error TS2546: Property 'pvt' has conflicting declarations and is inaccessible in type 'mixB.(Anonymous class) & A'. +tests/cases/compiler/mixinPrivateAndProtected.ts(54,5): error TS2445: Property 'ptd' is protected and only accessible within class 'mixC<{ new (...args: any[]): mixB.(Anonymous class); prototype: mixB.(Anonymous class); } & typeof A>.(Anonymous class) & mixB.(Anonymous class) & A' and its subclasses. +tests/cases/compiler/mixinPrivateAndProtected.ts(55,5): error TS2546: Property 'pvt' has conflicting declarations and is inaccessible in type 'mixC<{ new (...args: any[]): mixB.(Anonymous class); prototype: mixB.(Anonymous class); } & typeof A>.(Anonymous class) & mixB.(Anonymous class) & A'. +tests/cases/compiler/mixinPrivateAndProtected.ts(58,6): error TS2445: Property 'ptd' is protected and only accessible within class 'mixC<{ new (...args: any[]): mixB2.(Anonymous class); prototype: mixB2.(Anonymous class); } & typeof A>.(Anonymous class) & mixB2.(Anonymous class) & A' and its subclasses. +tests/cases/compiler/mixinPrivateAndProtected.ts(59,6): error TS2546: Property 'pvt' has conflicting declarations and is inaccessible in type 'mixC<{ new (...args: any[]): mixB2.(Anonymous class); prototype: mixB2.(Anonymous class); } & typeof A>.(Anonymous class) & mixB2.(Anonymous class) & A'. + + +==== tests/cases/compiler/mixinPrivateAndProtected.ts (8 errors) ==== + // Repro from #13830 + + type Constructor = new(...args: any[]) => T; + + class A { + public pb: number = 2; + protected ptd: number = 1; + private pvt: number = 0; + } + + function mixB>(Cls: T) { + return class extends Cls { + protected ptd: number = 10; + private pvt: number = 0; + }; + } + + function mixB2>(Cls: T) { + return class extends Cls { + protected ptd: number = 10; + }; + } + + const + AB = mixB(A), + AB2 = mixB2(A); + + function mixC>(Cls: T) { + return class extends Cls { + protected ptd: number = 100; + private pvt: number = 0; + }; + } + + const + AB2C = mixC(AB2), + ABC = mixC(AB); + + const + a = new A(), + ab = new AB(), + abc = new ABC(), + ab2c = new AB2C(); + + a.pb.toFixed(); + a.ptd.toFixed(); // Error + ~~~ +!!! error TS2445: Property 'ptd' is protected and only accessible within class 'A' and its subclasses. + a.pvt.toFixed(); // Error + ~~~ +!!! error TS2341: Property 'pvt' is private and only accessible within class 'A'. + + ab.pb.toFixed(); + ab.ptd.toFixed(); // Error + ~~~ +!!! error TS2445: Property 'ptd' is protected and only accessible within class 'mixB.(Anonymous class) & A' and its subclasses. + ab.pvt.toFixed(); // Error + ~~~ +!!! error TS2546: Property 'pvt' has conflicting declarations and is inaccessible in type 'mixB.(Anonymous class) & A'. + + abc.pb.toFixed(); + abc.ptd.toFixed(); // Error + ~~~ +!!! error TS2445: Property 'ptd' is protected and only accessible within class 'mixC<{ new (...args: any[]): mixB.(Anonymous class); prototype: mixB.(Anonymous class); } & typeof A>.(Anonymous class) & mixB.(Anonymous class) & A' and its subclasses. + abc.pvt.toFixed(); // Error + ~~~ +!!! error TS2546: Property 'pvt' has conflicting declarations and is inaccessible in type 'mixC<{ new (...args: any[]): mixB.(Anonymous class); prototype: mixB.(Anonymous class); } & typeof A>.(Anonymous class) & mixB.(Anonymous class) & A'. + + ab2c.pb.toFixed(); + ab2c.ptd.toFixed(); // Error + ~~~ +!!! error TS2445: Property 'ptd' is protected and only accessible within class 'mixC<{ new (...args: any[]): mixB2.(Anonymous class); prototype: mixB2.(Anonymous class); } & typeof A>.(Anonymous class) & mixB2.(Anonymous class) & A' and its subclasses. + ab2c.pvt.toFixed(); // Error + ~~~ +!!! error TS2546: Property 'pvt' has conflicting declarations and is inaccessible in type 'mixC<{ new (...args: any[]): mixB2.(Anonymous class); prototype: mixB2.(Anonymous class); } & typeof A>.(Anonymous class) & mixB2.(Anonymous class) & A'. + + // Repro from #13924 + + class Person { + constructor(public name: string) {} + + protected myProtectedFunction() { + // do something + } + } + + function PersonMixin>(Base: T) { + return class extends Base { + constructor(...args: any[]) { + super(...args); + } + + myProtectedFunction() { + super.myProtectedFunction(); + // do more things + } + }; + } + + class Customer extends PersonMixin(Person) { + accountBalance: number; + f() { + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/mixinPrivateAndProtected.js b/tests/baselines/reference/mixinPrivateAndProtected.js new file mode 100644 index 0000000000000..9b8f3ee0c128a --- /dev/null +++ b/tests/baselines/reference/mixinPrivateAndProtected.js @@ -0,0 +1,197 @@ +//// [mixinPrivateAndProtected.ts] +// Repro from #13830 + +type Constructor = new(...args: any[]) => T; + +class A { + public pb: number = 2; + protected ptd: number = 1; + private pvt: number = 0; +} + +function mixB>(Cls: T) { + return class extends Cls { + protected ptd: number = 10; + private pvt: number = 0; + }; +} + +function mixB2>(Cls: T) { + return class extends Cls { + protected ptd: number = 10; + }; +} + +const + AB = mixB(A), + AB2 = mixB2(A); + +function mixC>(Cls: T) { + return class extends Cls { + protected ptd: number = 100; + private pvt: number = 0; + }; +} + +const + AB2C = mixC(AB2), + ABC = mixC(AB); + +const + a = new A(), + ab = new AB(), + abc = new ABC(), + ab2c = new AB2C(); + +a.pb.toFixed(); +a.ptd.toFixed(); // Error +a.pvt.toFixed(); // Error + +ab.pb.toFixed(); +ab.ptd.toFixed(); // Error +ab.pvt.toFixed(); // Error + +abc.pb.toFixed(); +abc.ptd.toFixed(); // Error +abc.pvt.toFixed(); // Error + +ab2c.pb.toFixed(); +ab2c.ptd.toFixed(); // Error +ab2c.pvt.toFixed(); // Error + +// Repro from #13924 + +class Person { + constructor(public name: string) {} + + protected myProtectedFunction() { + // do something + } +} + +function PersonMixin>(Base: T) { + return class extends Base { + constructor(...args: any[]) { + super(...args); + } + + myProtectedFunction() { + super.myProtectedFunction(); + // do more things + } + }; +} + +class Customer extends PersonMixin(Person) { + accountBalance: number; + f() { + } +} + + +//// [mixinPrivateAndProtected.js] +// Repro from #13830 +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var A = (function () { + function A() { + this.pb = 2; + this.ptd = 1; + this.pvt = 0; + } + return A; +}()); +function mixB(Cls) { + return (function (_super) { + __extends(class_1, _super); + function class_1() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.ptd = 10; + _this.pvt = 0; + return _this; + } + return class_1; + }(Cls)); +} +function mixB2(Cls) { + return (function (_super) { + __extends(class_2, _super); + function class_2() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.ptd = 10; + return _this; + } + return class_2; + }(Cls)); +} +var AB = mixB(A), AB2 = mixB2(A); +function mixC(Cls) { + return (function (_super) { + __extends(class_3, _super); + function class_3() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.ptd = 100; + _this.pvt = 0; + return _this; + } + return class_3; + }(Cls)); +} +var AB2C = mixC(AB2), ABC = mixC(AB); +var a = new A(), ab = new AB(), abc = new ABC(), ab2c = new AB2C(); +a.pb.toFixed(); +a.ptd.toFixed(); // Error +a.pvt.toFixed(); // Error +ab.pb.toFixed(); +ab.ptd.toFixed(); // Error +ab.pvt.toFixed(); // Error +abc.pb.toFixed(); +abc.ptd.toFixed(); // Error +abc.pvt.toFixed(); // Error +ab2c.pb.toFixed(); +ab2c.ptd.toFixed(); // Error +ab2c.pvt.toFixed(); // Error +// Repro from #13924 +var Person = (function () { + function Person(name) { + this.name = name; + } + Person.prototype.myProtectedFunction = function () { + // do something + }; + return Person; +}()); +function PersonMixin(Base) { + return (function (_super) { + __extends(class_4, _super); + function class_4() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return _super.apply(this, args) || this; + } + class_4.prototype.myProtectedFunction = function () { + _super.prototype.myProtectedFunction.call(this); + // do more things + }; + return class_4; + }(Base)); +} +var Customer = (function (_super) { + __extends(Customer, _super); + function Customer() { + return _super !== null && _super.apply(this, arguments) || this; + } + Customer.prototype.f = function () { + }; + return Customer; +}(PersonMixin(Person))); diff --git a/tests/cases/compiler/mixinPrivateAndProtected.ts b/tests/cases/compiler/mixinPrivateAndProtected.ts new file mode 100644 index 0000000000000..5cbcdb1c7b6bd --- /dev/null +++ b/tests/cases/compiler/mixinPrivateAndProtected.ts @@ -0,0 +1,88 @@ +// Repro from #13830 + +type Constructor = new(...args: any[]) => T; + +class A { + public pb: number = 2; + protected ptd: number = 1; + private pvt: number = 0; +} + +function mixB>(Cls: T) { + return class extends Cls { + protected ptd: number = 10; + private pvt: number = 0; + }; +} + +function mixB2>(Cls: T) { + return class extends Cls { + protected ptd: number = 10; + }; +} + +const + AB = mixB(A), + AB2 = mixB2(A); + +function mixC>(Cls: T) { + return class extends Cls { + protected ptd: number = 100; + private pvt: number = 0; + }; +} + +const + AB2C = mixC(AB2), + ABC = mixC(AB); + +const + a = new A(), + ab = new AB(), + abc = new ABC(), + ab2c = new AB2C(); + +a.pb.toFixed(); +a.ptd.toFixed(); // Error +a.pvt.toFixed(); // Error + +ab.pb.toFixed(); +ab.ptd.toFixed(); // Error +ab.pvt.toFixed(); // Error + +abc.pb.toFixed(); +abc.ptd.toFixed(); // Error +abc.pvt.toFixed(); // Error + +ab2c.pb.toFixed(); +ab2c.ptd.toFixed(); // Error +ab2c.pvt.toFixed(); // Error + +// Repro from #13924 + +class Person { + constructor(public name: string) {} + + protected myProtectedFunction() { + // do something + } +} + +function PersonMixin>(Base: T) { + return class extends Base { + constructor(...args: any[]) { + super(...args); + } + + myProtectedFunction() { + super.myProtectedFunction(); + // do more things + } + }; +} + +class Customer extends PersonMixin(Person) { + accountBalance: number; + f() { + } +} diff --git a/tests/cases/conformance/classes/mixinAccessModifiers.ts b/tests/cases/conformance/classes/mixinAccessModifiers.ts new file mode 100644 index 0000000000000..a628371eec6bb --- /dev/null +++ b/tests/cases/conformance/classes/mixinAccessModifiers.ts @@ -0,0 +1,108 @@ +// @declaration: true + +type Constructable = new (...args: any[]) => object; + +class Private { + constructor (...args: any[]) {} + private p: string; +} + +class Private2 { + constructor (...args: any[]) {} + private p: string; +} + +class Protected { + constructor (...args: any[]) {} + protected p: string; + protected static s: string; +} + +class Protected2 { + constructor (...args: any[]) {} + protected p: string; + protected static s: string; +} + +class Public { + constructor (...args: any[]) {} + public p: string; + public static s: string; +} + +class Public2 { + constructor (...args: any[]) {} + public p: string; + public static s: string; +} + +function f1(x: Private & Private2) { + x.p; // Error, private constituent makes property inaccessible +} + +function f2(x: Private & Protected) { + x.p; // Error, private constituent makes property inaccessible +} + +function f3(x: Private & Public) { + x.p; // Error, private constituent makes property inaccessible +} + +function f4(x: Protected & Protected2) { + x.p; // Error, protected when all constituents are protected +} + +function f5(x: Protected & Public) { + x.p; // Ok, public if any constituent is public +} + +function f6(x: Public & Public2) { + x.p; // Ok, public if any constituent is public +} + +declare function Mix(c1: T, c2: U): T & U; + +// Can't derive from type with inaccessible properties + +class C1 extends Mix(Private, Private2) {} +class C2 extends Mix(Private, Protected) {} +class C3 extends Mix(Private, Public) {} + +class C4 extends Mix(Protected, Protected2) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; + c5.p; + c6.p; + } + static g() { + C4.s; + C5.s; + C6.s + } +} + +class C5 extends Mix(Protected, Public) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; // Error, not in class deriving from Protected2 + c5.p; + c6.p; + } + static g() { + C4.s; // Error, not in class deriving from Protected2 + C5.s; + C6.s + } +} + +class C6 extends Mix(Public, Public2) { + f(c4: C4, c5: C5, c6: C6) { + c4.p; // Error, not in class deriving from Protected2 + c5.p; + c6.p; + } + static g() { + C4.s; // Error, not in class deriving from Protected2 + C5.s; + C6.s + } +}