Skip to content

Commit

Permalink
Merge tag 'refs/tags/1.1.388' into merge-1.1.388
Browse files Browse the repository at this point in the history
# Conflicts:
#	lerna.json
#	packages/pyright-internal/package-lock.json
#	packages/pyright-internal/src/localization/package.nls.ru.json
#	packages/pyright-internal/src/localization/package.nls.zh-cn.json
#	packages/pyright-internal/src/tests/samples/dataclassTransform3.py
#	packages/pyright/package-lock.json
#	packages/pyright/package.json
#	packages/vscode-pyright/package-lock.json
#	packages/vscode-pyright/package.json
  • Loading branch information
DetachHead committed Nov 6, 2024
2 parents a162d08 + 0e5eb7a commit c83ffea
Show file tree
Hide file tree
Showing 26 changed files with 480 additions and 341 deletions.
2 changes: 1 addition & 1 deletion packages/pyright-internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pyright-internal",
"displayName": "pyright",
"description": "Type checker for the Python language",
"version": "1.1.387",
"version": "1.1.388",
"license": "MIT",
"private": true,
"files": [
Expand Down
24 changes: 5 additions & 19 deletions packages/pyright-internal/src/analyzer/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { DiagnosticLevel } from '../common/configOptions';
import { assert, assertNever, fail } from '../common/debug';
import { CreateTypeStubFileAction, Diagnostic, DiagnosticAddendum } from '../common/diagnostic';
import { DiagnosticRule } from '../common/diagnosticRules';
import { DocStringService } from '../common/docStringService';
import { stripFileExtension } from '../common/pathUtils';
import { convertTextRangeToRange } from '../common/positionUtils';
import { TextRange, getEmptyRange } from '../common/textRange';
Expand Down Expand Up @@ -256,11 +255,7 @@ export class Binder extends ParseTreeWalker {
// the current function.
private _codeFlowComplexity = 0;

constructor(
fileInfo: AnalyzerFileInfo,
private _docStringService: DocStringService,
private _moduleSymbolOnly = false
) {
constructor(fileInfo: AnalyzerFileInfo, private _moduleSymbolOnly = false) {
super();

this._fileInfo = fileInfo;
Expand Down Expand Up @@ -565,15 +560,6 @@ export class Binder extends ParseTreeWalker {
if (paramNode.d.name) {
const symbol = this._bindNameToScope(this._currentScope, paramNode.d.name);

// Extract the parameter docString from the function docString
let docString = ParseTreeUtils.getDocString(node?.d.suite?.d.statements ?? []);
if (docString !== undefined) {
docString = this._docStringService.extractParameterDocumentation(
docString,
paramNode.d.name.d.value
);
}

if (symbol) {
const paramDeclaration: ParamDeclaration = {
type: DeclarationType.Param,
Expand All @@ -582,7 +568,6 @@ export class Binder extends ParseTreeWalker {
range: convertTextRangeToRange(paramNode, this._fileInfo.lines),
moduleName: this._fileInfo.moduleName,
isInExceptSuite: this._isInExceptSuite,
docString: docString,
};

symbol.addDeclaration(paramDeclaration);
Expand Down Expand Up @@ -4095,9 +4080,10 @@ export class Binder extends ParseTreeWalker {
// To determine whether the first parameter of the method
// refers to the class or the instance, we need to apply
// some heuristics.
if (methodNode.d.name.d.value === '__new__') {
// The __new__ method is special. It acts as a classmethod even
// though it doesn't have a @classmethod decorator.
const implicitClassMethods = ['__new__', '__init_subclass__', '__class_getitem__'];
if (implicitClassMethods.includes(methodNode.d.name.d.value)) {
// Several methods are special. They act as class methods even
// though they don't have a @classmethod decorator.
isInstanceMember = false;
} else {
// Assume that it's an instance member unless we find
Expand Down
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/analyzer/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5329,6 +5329,12 @@ export class Checker extends ParseTreeWalker {
return;
}

// Skip type variables that have been internally synthesized
// for a variety of reasons.
if (param.shared.isSynthesized) {
return;
}

// Skip type variables with auto-variance.
if (param.shared.declaredVariance === Variance.Auto) {
return;
Expand Down
27 changes: 26 additions & 1 deletion packages/pyright-internal/src/analyzer/constructorTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ArgCategory, ExpressionNode, ParamCategory } from '../parser/parseNodes
import { ConstraintTracker } from './constraintTracker';
import { createFunctionFromConstructor } from './constructors';
import { getParamListDetails, ParamKind } from './parameterUtils';
import { getTypedDictMembersForClass } from './typedDicts';
import { Arg, FunctionResult, TypeEvaluator } from './typeEvaluatorTypes';
import {
AnyType,
Expand All @@ -31,8 +32,10 @@ import {
isOverloaded,
isTypeSame,
isTypeVar,
isUnpackedClass,
OverloadedType,
Type,
TypedDictEntry,
} from './types';
import { convertToInstance, lookUpObjectMember, makeInferenceContext, MemberAccessFlags } from './typeUtils';

Expand Down Expand Up @@ -404,7 +407,29 @@ function applyPartialTransformToFunction(
// Create a new parameter list that omits parameters that have been
// populated already.
const updatedParamList: FunctionParam[] = specializedFunctionType.shared.parameters.map((param, index) => {
const newType = FunctionType.getParamType(specializedFunctionType, index);
let newType = FunctionType.getParamType(specializedFunctionType, index);

// If this is an **kwargs with an unpacked TypedDict, mark the provided
// TypedDict entries as provided.
if (
param.category === ParamCategory.KwargsDict &&
isClassInstance(newType) &&
isUnpackedClass(newType) &&
ClassType.isTypedDictClass(newType)
) {
const typedDictEntries = getTypedDictMembersForClass(evaluator, newType);
const narrowedEntriesMap = new Map<string, TypedDictEntry>(newType.priv.typedDictNarrowedEntries ?? []);

typedDictEntries.knownItems.forEach((entry, name) => {
if (paramMap.has(name)) {
narrowedEntriesMap.set(name, { ...entry, isRequired: false });
}
});

newType = ClassType.cloneAsInstance(
ClassType.cloneForNarrowedTypedDictEntries(newType, narrowedEntriesMap)
);
}

// If it's a keyword parameter that has been assigned a value through
// the "partial" mechanism, mark it has having a default value.
Expand Down
4 changes: 0 additions & 4 deletions packages/pyright-internal/src/analyzer/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ export interface ParamDeclaration extends DeclarationBase {
type: DeclarationType.Param;
node: ParameterNode;

// Documentation specified in the function's docstring (if any) can be
// associated with the parameter
docString?: string;

// Inferred parameters can be inferred from pieces of an actual NameNode, so this
// value represents the actual 'name' as the user thinks of it.
inferredName?: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/pyright-internal/src/analyzer/parameterUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ export function getParamListDetails(type: FunctionType): ParamListDetails {

const typedDictType = paramType;
paramType.shared.typedDictEntries.knownItems.forEach((entry, name) => {
entry = paramType.priv.typedDictNarrowedEntries?.get(name) ?? entry;

const specializedParamType = partiallySpecializeType(
entry.valueType,
typedDictType,
Expand Down
6 changes: 1 addition & 5 deletions packages/pyright-internal/src/analyzer/sourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,11 +829,7 @@ export class SourceFile {
);
AnalyzerNodeInfo.setFileInfo(this._writableData.parserOutput!.parseTree, fileInfo);

const binder = new Binder(
fileInfo,
this.serviceProvider.docStringService(),
configOptions.indexGenerationMode
);
const binder = new Binder(fileInfo, configOptions.indexGenerationMode);
this._writableData.isBindingInProgress = true;
binder.bindModule(this._writableData.parserOutput!.parseTree);

Expand Down
34 changes: 26 additions & 8 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3783,10 +3783,11 @@ export function createTypeEvaluator(
const inheritedSlotsNames = ClassType.getInheritedSlotsNames(memberClass);

if (inheritedSlotsNames && memberClass.shared.localSlotsNames) {
// Skip this check if the local slots is specified but empty because this pattern
// is used in a legitimate manner for mix-in classes.
// Skip this check if the local slots is specified but empty
// and the class isn't final. This pattern is used in a
// legitimate manner for mix-in classes.
if (
memberClass.shared.localSlotsNames.length > 0 &&
(memberClass.shared.localSlotsNames.length > 0 || ClassType.isFinal(memberClass)) &&
!inheritedSlotsNames.some((name) => name === memberName)
) {
// Determine whether the assignment corresponds to a descriptor
Expand Down Expand Up @@ -17474,8 +17475,7 @@ export function createTypeEvaluator(
arg.d.valueExpr
);
}
genericTypeParams = [];
addTypeVarsToListIfUnique(genericTypeParams, getTypeVarArgsRecursive(argType));
genericTypeParams = buildTypeParamsFromTypeArgs(argType);
}
}
} else if (
Expand All @@ -17491,8 +17491,7 @@ export function createTypeEvaluator(
arg.d.valueExpr
);
}
protocolTypeParams = [];
addTypeVarsToListIfUnique(protocolTypeParams, getTypeVarArgsRecursive(argType));
protocolTypeParams = buildTypeParamsFromTypeArgs(argType);

if (node.d.typeParams && protocolTypeParams.length > 0) {
addDiagnostic(
Expand Down Expand Up @@ -17997,6 +17996,25 @@ export function createTypeEvaluator(
});
}

function buildTypeParamsFromTypeArgs(classType: ClassType): TypeVarType[] {
const typeParams: TypeVarType[] = [];
const typeArgs = classType.priv.typeArgs ?? [];

typeArgs.forEach((typeArg, index) => {
if (isTypeVar(typeArg)) {
typeParams.push(typeArg);
return;
}

// Synthesize a dummy type parameter.
const typeVar = TypeVarType.createInstance(`__P${index}`);
typeVar.shared.isSynthesized = true;
typeParams.push(typeVar);
});

return typeParams;
}

// Determines whether the type parameters has a default that refers to another
// type parameter. If so, validates that it is in the list of "live" type
// parameters and updates the scope of the type parameter referred to in the
Expand Down Expand Up @@ -24120,7 +24138,7 @@ export function createTypeEvaluator(
effectiveFlags = flags | AssignTypeFlags.RetainLiteralsForTypeVar;
errorSource = LocAddendum.typeVarIsCovariant;
} else if (variance === Variance.Contravariant) {
effectiveFlags = (flags ^ AssignTypeFlags.Contravariant) | AssignTypeFlags.RetainLiteralsForTypeVar;
effectiveFlags = flags | AssignTypeFlags.Contravariant | AssignTypeFlags.RetainLiteralsForTypeVar;
errorSource = LocAddendum.typeVarIsContravariant;
} else {
effectiveFlags = flags | AssignTypeFlags.Invariant | AssignTypeFlags.RetainLiteralsForTypeVar;
Expand Down
64 changes: 41 additions & 23 deletions packages/pyright-internal/src/analyzer/typeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,11 @@ export function getTypeVarArgsRecursive(type: Type, recursionCount = 0): TypeVar
return [];
}

// Don't return any bound type variables.
if (TypeVarType.isBound(type)) {
return [];
}

// Don't return any P.args or P.kwargs types.
if (isParamSpec(type) && type.priv.paramSpecAccess) {
return [TypeVarType.cloneForParamSpecAccess(type, /* access */ undefined)];
Expand Down Expand Up @@ -4197,30 +4202,43 @@ class ApplySolvedTypeVarsTransformer extends TypeVarTransformer {
// in cases where TypeVars can go unsolved due to unions in parameter
// annotations, like this:
// def test(x: Union[str, T]) -> Union[str, T]
if (this._options.replaceUnsolved?.eliminateUnsolvedInUnions) {
if (
isTypeVar(preTransform) &&
this._shouldReplaceTypeVar(preTransform) &&
this._shouldReplaceUnsolvedTypeVar(preTransform)
) {
const solutionSet = this._solution.getSolutionSet(this._activeConstraintSetIndex ?? 0);
const typeVarType = solutionSet.getType(preTransform);

// Did the TypeVar remain unsolved?
if (!typeVarType || (isTypeVar(typeVarType) && TypeVarType.isUnification(typeVarType))) {
// If the TypeVar was not transformed, then it was unsolved,
// and we'll eliminate it.
if (preTransform === postTransform) {
return undefined;
}
if (!this._options.replaceUnsolved?.eliminateUnsolvedInUnions) {
return postTransform;
}

// If useDefaultForUnsolved or useUnknownForUnsolved is true, the postTransform type will
// be Unknown, which we want to eliminate.
if (this._options.replaceUnsolved) {
if (isUnknown(postTransform)) {
return undefined;
}
}
const solutionSet = this._solution.getSolutionSet(this._activeConstraintSetIndex ?? 0);

if (isTypeVar(preTransform)) {
if (!this._shouldReplaceTypeVar(preTransform) || !this._shouldReplaceUnsolvedTypeVar(preTransform)) {
return postTransform;
}

const typeVarType = solutionSet.getType(preTransform);

// Did the TypeVar remain unsolved?
if (typeVarType) {
if (!isTypeVar(typeVarType) || !TypeVarType.isUnification(typeVarType)) {
return postTransform;
}
}

// If the TypeVar was not transformed, then it was unsolved,
// and we'll eliminate it.
if (preTransform === postTransform) {
return undefined;
}

// If useDefaultForUnsolved or useUnknownForUnsolved is true, the postTransform type will
// be Unknown, which we want to eliminate.
if (this._options.replaceUnsolved && isUnknown(postTransform)) {
return undefined;
}
} else if (preTransform.props?.condition) {
// If this is a type that is conditioned on a unification TypeVar,
// see if TypeVar was solved. If not, eliminate the type.
for (const condition of preTransform.props.condition) {
if (TypeVarType.isUnification(condition.typeVar) && !solutionSet.getType(condition.typeVar)) {
return undefined;
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion packages/pyright-internal/src/analyzer/typedDicts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,16 @@ export function synthesizeTypedDictClassMethods(
const mappingValueType = getTypedDictMappingEquivalent(evaluator, classType);

if (mappingValueType) {
let keyValueType: Type = strType;

// If we know that there can be no more items, we can provide
// a more accurate key type consisting of all known keys.
if (entries.extraItems && isNever(entries.extraItems.valueType)) {
keyValueType = combineTypes(
Array.from(entries.knownItems.keys()).map((key) => ClassType.cloneWithLiteral(strType, key))
);
}

['items', 'keys', 'values'].forEach((methodName) => {
const method = FunctionType.createSynthesizedInstance(methodName);
FunctionType.addParam(method, selfParam);
Expand All @@ -773,7 +783,7 @@ export function synthesizeTypedDictClassMethods(
) {
method.shared.declaredReturnType = ClassType.specialize(
ClassType.cloneAsInstance(returnTypeClass),
[strType, mappingValueType]
[keyValueType, mappingValueType]
);

symbolTable.set(methodName, Symbol.createWithType(SymbolFlags.ClassMember, method));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import {
TypeAnnotationNode,
} from '../parser/parseNodes';
import { ParseFileResults } from '../parser/parser';
import { Tokenizer } from '../parser/tokenizer';
import {
FStringStartToken,
OperatorToken,
Expand Down Expand Up @@ -2897,7 +2898,10 @@ export class CompletionProvider {
paramInfo.kind !== ParamKind.Positional &&
paramInfo.kind !== ParamKind.ExpandedArgs
) {
if (!SymbolNameUtils.isPrivateOrProtectedName(paramInfo.param.name)) {
if (
!SymbolNameUtils.isPrivateOrProtectedName(paramInfo.param.name) &&
Tokenizer.isPythonIdentifier(paramInfo.param.name)
) {
names.add(paramInfo.param.name);
}
}
Expand Down
Loading

0 comments on commit c83ffea

Please sign in to comment.