1313// See the Apache Version 2.0 License for specific language governing
1414// permissions and limitations under the License.
1515
16- using System . Collections . Generic ;
17- using System . Diagnostics ;
18- using System . Linq ;
19- using System . Threading ;
2016using Microsoft . Python . Analysis . Analyzer . Evaluation ;
2117using Microsoft . Python . Analysis . Documents ;
22- using Microsoft . Python . Analysis . Modules ;
23- using Microsoft . Python . Analysis . Specializations . Typing ;
2418using Microsoft . Python . Analysis . Types ;
2519using Microsoft . Python . Analysis . Types . Collections ;
2620using Microsoft . Python . Analysis . Values ;
2721using Microsoft . Python . Core ;
2822using Microsoft . Python . Core . Diagnostics ;
2923using Microsoft . Python . Parsing . Ast ;
24+ using System . Collections . Generic ;
25+ using System . Diagnostics ;
26+ using System . Linq ;
27+ using System . Threading ;
3028
3129namespace Microsoft . Python . Analysis . Analyzer {
3230 [ DebuggerDisplay ( "{Module.Name} : {Module.ModuleType}" ) ]
@@ -205,7 +203,7 @@ public void Complete() {
205203
206204 SymbolTable . EvaluateAll ( ) ;
207205 SymbolTable . ReplacedByStubs . Clear ( ) ;
208- MergeStub ( ) ;
206+ new StubMerger ( Eval ) . MergeStub ( _stubAnalysis , _cancellationToken ) ;
209207
210208 if ( _allIsUsable && _allReferencesCount >= 1 && GlobalScope . Variables . TryGetVariable ( AllVariableName , out var variable )
211209 && variable ? . Value is IPythonCollection collection && collection . IsExact ) {
@@ -221,217 +219,5 @@ public void Complete() {
221219
222220 public GlobalScope GlobalScope => Eval . GlobalScope ;
223221 public IReadOnlyList < string > StarImportMemberNames { get ; private set ; }
224-
225- /// <summary>
226- /// Merges data from stub with the data from the module.
227- /// </summary>
228- /// <remarks>
229- /// Functions are taken from the stub by the function walker since
230- /// information on the return type is needed during the analysis walk.
231- /// However, if the module is compiled (scraped), it often lacks some
232- /// of the definitions. Stub may contains those so we need to merge it in.
233- /// </remarks>
234- private void MergeStub ( ) {
235- _cancellationToken . ThrowIfCancellationRequested ( ) ;
236-
237- if ( Module . ModuleType == ModuleType . User || Module . ModuleType == ModuleType . Stub ) {
238- return ;
239- }
240- // No stub, no merge.
241- if ( _stubAnalysis . IsEmpty ( ) ) {
242- return ;
243- }
244- // TODO: figure out why module is getting analyzed before stub.
245- // https://github.com/microsoft/python-language-server/issues/907
246- // Debug.Assert(!(_stubAnalysis is EmptyAnalysis));
247-
248- // Note that scrape can pick up more functions than the stub contains
249- // Or the stub can have definitions that scraping had missed. Therefore
250- // merge is the combination of the two with the documentation coming
251- // from the library source of from the scraped module.
252- foreach ( var v in _stubAnalysis . GlobalScope . Variables ) {
253- var stubType = v . Value . GetPythonType ( ) ;
254- if ( stubType . IsUnknown ( ) ) {
255- continue ;
256- }
257-
258- var sourceVar = Eval . GlobalScope . Variables [ v . Name ] ;
259- var sourceType = sourceVar ? . Value . GetPythonType ( ) ;
260-
261- // If stub says 'Any' but we have better type, keep the current type.
262- if ( stubType . DeclaringModule is TypingModule && stubType . Name == "Any" ) {
263- continue ;
264- }
265-
266- if ( sourceVar ? . Source == VariableSource . Import &&
267- sourceVar . GetPythonType ( ) ? . DeclaringModule . Stub != null ) {
268- // Keep imported types as they are defined in the library. For example,
269- // 'requests' imports NullHandler as 'from logging import NullHandler'.
270- // But 'requests' also declares NullHandler in its stub (but not in the main code)
271- // and that declaration does not have documentation or location. Therefore avoid
272- // taking types that are stub-only when similar type is imported from another
273- // module that also has a stub.
274- continue ;
275- }
276-
277- TryReplaceMember ( v , sourceType , stubType ) ;
278- }
279-
280- UpdateVariables ( ) ;
281- }
282-
283- private void TryReplaceMember ( IVariable v , IPythonType sourceType , IPythonType stubType ) {
284- // If type does not exist in module, but exists in stub, declare it unless it is an import.
285- // If types are the classes, take class from the stub, then add missing members.
286- // Otherwise, replace type by one from the stub.
287- switch ( sourceType ) {
288- case null :
289- // Nothing in sources, but there is type in the stub. Declare it.
290- if ( v . Source == VariableSource . Declaration || v . Source == VariableSource . Generic ) {
291- Eval . DeclareVariable ( v . Name , v . Value , v . Source ) ;
292- }
293- break ;
294-
295- case PythonClassType sourceClass when Module . Equals ( sourceClass . DeclaringModule ) :
296- // Transfer documentation first so we get class documentation
297- // that came from class definition win over one that may
298- // come from __init__ during the member merge below.
299- TransferDocumentationAndLocation ( sourceClass , stubType ) ;
300-
301- // Replace the class entirely since stub members may use generic types
302- // and the class definition is important. We transfer missing members
303- // from the original class to the stub.
304- Eval . DeclareVariable ( v . Name , v . Value , v . Source ) ;
305-
306- // Go through source class members and pick those that are
307- // not present in the stub class.
308- foreach ( var name in sourceClass . GetMemberNames ( ) ) {
309-
310- var sourceMember = sourceClass . GetMember ( name ) ;
311- if ( sourceMember . IsUnknown ( ) ) {
312- continue ; // Anything is better than unknowns.
313- }
314- var sourceMemberType = sourceMember ? . GetPythonType ( ) ;
315-
316- var stubMember = stubType . GetMember ( name ) ;
317- var stubMemberType = stubMember . GetPythonType ( ) ;
318-
319- // Don't augment types that do not come from this module.
320- if ( sourceType . IsBuiltin || stubType . IsBuiltin ) {
321- // If source type does not have an immediate member such as __init__() and
322- // rather have it inherited from object, we do not want to use the inherited
323- // since stub class may either have its own of inherits it from the object.
324- continue ;
325- }
326-
327- if ( stubMemberType ? . MemberType == PythonMemberType . Method && stubMemberType ? . DeclaringModule . ModuleType == ModuleType . Builtins ) {
328- // Leave methods coming from object at the object and don't copy them into the derived class.
329- }
330-
331- // Get documentation from the current type, if any, since stubs
332- // typically do not contain documentation while scraped code does.
333- TransferDocumentationAndLocation ( sourceMemberType , stubMemberType ) ;
334-
335- // If stub says 'Any' but we have better type, use member from the original class.
336- if ( stubMember != null && ! ( stubType . DeclaringModule is TypingModule && stubType . Name == "Any" ) ) {
337- continue ; // Stub already have the member, don't replace.
338- }
339-
340- ( stubType as PythonType ) ? . AddMember ( name , stubMember , overwrite : true ) ;
341- }
342- break ;
343-
344- case IPythonModule _:
345- // We do not re-declare modules.
346- break ;
347-
348- default :
349- var stubModule = stubType . DeclaringModule ;
350- if ( stubType is IPythonModule || stubModule . ModuleType == ModuleType . Builtins ) {
351- // Modules members that are modules should remain as they are, i.e. os.path
352- // should remain library with its own stub attached.
353- break ;
354- }
355- // We do not re-declaring variables that are imported.
356- if ( v . Source == VariableSource . Declaration ) {
357- // Re-declare variable with the data from the stub.
358- TransferDocumentationAndLocation ( sourceType , stubType ) ;
359- // TODO: choose best type between the scrape and the stub. Stub probably should always win.
360- var source = Eval . CurrentScope . Variables [ v . Name ] ? . Source ?? v . Source ;
361- Eval . DeclareVariable ( v . Name , v . Value , source ) ;
362- }
363-
364- break ;
365- }
366- }
367-
368- private void UpdateVariables ( ) {
369- // Second pass: if we replaced any classes by new from the stub, we need to update
370- // variables that may still be holding old content. For example, ctypes
371- // declares 'c_voidp = c_void_p' so when we replace 'class c_void_p'
372- // by class from the stub, we need to go and update 'c_voidp' variable.
373- foreach ( var v in GlobalScope . Variables ) {
374- var variableType = v . Value . GetPythonType ( ) ;
375- if ( ! variableType . DeclaringModule . Equals ( Module ) && ! variableType . DeclaringModule . Equals ( Module . Stub ) ) {
376- continue ;
377- }
378- // Check if type that the variable references actually declared here.
379- var typeInScope = GlobalScope . Variables [ variableType . Name ] . GetPythonType ( ) ;
380- if ( typeInScope == null || variableType == typeInScope ) {
381- continue ;
382- }
383-
384- if ( v . Value == variableType ) {
385- Eval . DeclareVariable ( v . Name , typeInScope , v . Source ) ;
386- } else if ( v . Value is IPythonInstance ) {
387- Eval . DeclareVariable ( v . Name , new PythonInstance ( typeInScope ) , v . Source ) ;
388- }
389- }
390- }
391-
392- private static void TransferDocumentationAndLocation ( IPythonType s , IPythonType d ) {
393- if ( s . IsUnknown ( ) || s . IsBuiltin || d == null || d . IsBuiltin ) {
394- return ; // Do not transfer location of unknowns or builtins
395- }
396-
397- if ( d . DeclaringModule != s . DeclaringModule . Stub ) {
398- return ; // Do not change unrelated types.
399- }
400-
401- // Documentation and location are always get transferred from module type
402- // to the stub type and never the other way around. This makes sure that
403- // we show documentation from the original module and goto definition
404- // navigates to the module source and not to the stub.
405- if ( s != d && s is PythonType src && d is PythonType dst ) {
406- // If type is a class, then doc can either come from class definition node of from __init__.
407- // If class has doc from the class definition, don't stomp on it.
408- if ( src is PythonClassType srcClass && dst is PythonClassType dstClass ) {
409- // Higher lever source wins
410- if ( srcClass . DocumentationSource == PythonClassType . ClassDocumentationSource . Class ||
411- ( srcClass . DocumentationSource == PythonClassType . ClassDocumentationSource . Init && dstClass . DocumentationSource == PythonClassType . ClassDocumentationSource . Base ) ) {
412- dstClass . SetDocumentation ( srcClass . Documentation ) ;
413- }
414- } else {
415- // Sometimes destination (stub type) already has documentation. This happens when stub type
416- // is used to augment more than one type. For example, in threading module RLock stub class
417- // replaces both RLock function and _RLock class making 'factory' function RLock to look
418- // like a class constructor. Effectively a single stub type is used for more than one type
419- // in the source and two source types may have different documentation. Thus transferring doc
420- // from one source type affects documentation of another type. It may be better to clone stub
421- // type and separate instances for separate source type, but for now we'll just avoid stomping
422- // on the existing documentation.
423- if ( string . IsNullOrEmpty ( dst . Documentation ) ) {
424- var srcDocumentation = src . Documentation ;
425- if ( ! string . IsNullOrEmpty ( srcDocumentation ) ) {
426- dst . SetDocumentation ( srcDocumentation ) ;
427- }
428- }
429- }
430-
431- if ( src . Location . IsValid ) {
432- dst . Location = src . Location ;
433- }
434- }
435- }
436222 }
437223}
0 commit comments