Skip to content

Commit

Permalink
automatically import IPython.display.display in notebooks to match …
Browse files Browse the repository at this point in the history
…runtime and pylance
  • Loading branch information
DetachHead committed Feb 23, 2025
1 parent aea3ee0 commit 77e8fa4
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 28 deletions.
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/analyzer/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import * as ParseTreeUtils from './parseTreeUtils';
import { ParseTreeWalker } from './parseTreeWalker';
import { moduleIsInList } from './pythonPathUtils';
import { NameBindingType, Scope, ScopeType } from './scope';
import { IPythonMode } from './sourceFile';
import * as StaticExpressions from './staticExpressions';
import { Symbol, SymbolFlags, indeterminateSymbolId } from './symbol';
import { isConstantName, isPrivateName, isPrivateOrProtectedName } from './symbolNameUtils';
Expand Down Expand Up @@ -294,6 +295,11 @@ export class Binder extends ParseTreeWalker {
this._addImplicitSymbolToCurrentScope('__annotations__', node, 'Dict[str, Any]');
this._addImplicitSymbolToCurrentScope('__builtins__', node, 'Any');
this._addImplicitSymbolToCurrentScope('__doc__', node, 'str | None');
if (this._fileInfo.ipythonMode === IPythonMode.CellDocs) {
// this function is automatically available globally inside notebooks.
// https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.display
this._addImplicitSymbolToCurrentScope('display', node, 'IPython.display.display');
}

// Create a start node for the module.
this._currentFlowNode = this._createStartFlowNode();
Expand Down
10 changes: 9 additions & 1 deletion packages/pyright-internal/src/analyzer/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ export const enum DeclarationType {
Alias,
}

export type IntrinsicType = 'Any' | 'str' | 'str | None' | 'int' | 'Iterable[str]' | 'type[self]' | 'Dict[str, Any]';
export type IntrinsicType =
| 'Any'
| 'str'
| 'str | None'
| 'int'
| 'Iterable[str]'
| 'type[self]'
| 'Dict[str, Any]'
| 'IPython.display.display';

export interface DeclarationBase {
// Category of this symbol (function, variable, etc.).
Expand Down
67 changes: 40 additions & 27 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22299,39 +22299,52 @@ export function createTypeEvaluator(
: UnknownType.create(),
};
}

const strType = getBuiltInObject(declaration.node, 'str');
const intType = getBuiltInObject(declaration.node, 'int');
if (isClassInstance(intType) && isClassInstance(strType)) {
if (declaration.intrinsicType === 'str') {
return { type: strType };
if (declaration.intrinsicType === 'IPython.display.display') {
const fileInfo = AnalyzerNodeInfo.getFileInfo(declaration.node);
const lookupResult = fileInfo.importLookup({
importingFileUri: fileInfo.fileUri,
nameParts: ['IPython', 'display'],
});
if (lookupResult) {
const symbol = lookupResult.symbolTable.get('display');
if (symbol) {
return { type: getEffectiveTypeOfSymbol(symbol) };
}
}
} else {
const strType = getBuiltInObject(declaration.node, 'str');
const intType = getBuiltInObject(declaration.node, 'int');
if (isClassInstance(intType) && isClassInstance(strType)) {
if (declaration.intrinsicType === 'str') {
return { type: strType };
}

if (declaration.intrinsicType === 'str | None') {
return { type: combineTypes([strType, getNoneType()]) };
}
if (declaration.intrinsicType === 'str | None') {
return { type: combineTypes([strType, getNoneType()]) };
}

if (declaration.intrinsicType === 'int') {
return { type: intType };
}
if (declaration.intrinsicType === 'int') {
return { type: intType };
}

if (declaration.intrinsicType === 'Iterable[str]') {
const iterableType = getBuiltInType(declaration.node, 'Iterable');
if (isInstantiableClass(iterableType)) {
return {
type: ClassType.cloneAsInstance(ClassType.specialize(iterableType, [strType])),
};
if (declaration.intrinsicType === 'Iterable[str]') {
const iterableType = getBuiltInType(declaration.node, 'Iterable');
if (isInstantiableClass(iterableType)) {
return {
type: ClassType.cloneAsInstance(ClassType.specialize(iterableType, [strType])),
};
}
}
}

if (declaration.intrinsicType === 'Dict[str, Any]') {
const dictType = getBuiltInType(declaration.node, 'dict');
if (isInstantiableClass(dictType)) {
return {
type: ClassType.cloneAsInstance(
ClassType.specialize(dictType, [strType, AnyType.create()])
),
};
if (declaration.intrinsicType === 'Dict[str, Any]') {
const dictType = getBuiltInType(declaration.node, 'dict');
if (isInstantiableClass(dictType)) {
return {
type: ClassType.cloneAsInstance(
ClassType.specialize(dictType, [strType, AnyType.create()])
),
};
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/pyright-internal/src/tests/notebooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ test('invalid notebook file', () => {
/failed to parse jupyter notebook .* - SyntaxError: Unexpected token .*/
);
});

test('IPython.display.display automatically imported', () => {
const analysisResults = typeAnalyzeSampleFiles(['ipython_display_import/notebook.ipynb']);
validateResultsButBased(analysisResults, {
errors: [{ code: DiagnosticRule.reportUndefinedVariable, line: 4 }],
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
fake stub for `IPython.display` so we don't have to use the real one and the test doesn't have to run inside a venv.
see https://github.com/DetachHead/basedpyright/issues/994
"""
def display() -> None: ...
def display_html() -> None: ...
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from typing import Callable, assert_type\n",
"\n",
"assert_type(display, Callable[[], None])\n",
"# ensure that only display is imported, not the whole module\n",
"display_html"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

0 comments on commit 77e8fa4

Please sign in to comment.