From 2d11b74a7bb3b833fe88184c1297b997d0e1b4ac Mon Sep 17 00:00:00 2001 From: mrward Date: Tue, 5 Oct 2010 21:15:38 +0100 Subject: [PATCH] IronPython class fields defined in constructor now appear in code completion after typing 'self'. --- .../Project/PythonBinding.csproj | 1 + .../Project/Src/PythonAstWalker.cs | 21 ++++-- .../Project/Src/PythonClassFields.cs | 64 +++++++++++++++++++ .../Project/Src/PythonMemberResolver.cs | 14 ++++ .../Project/Src/PythonResolverContext.cs | 7 ++ .../Project/Src/PythonSelfResolver.cs | 7 +- .../Parsing/PythonParserParseFieldTests.cs | 57 +++++++++++++++++ .../Test/PythonBinding.Tests.csproj | 1 + .../Test/Resolver/ResolveSelfTests.cs | 47 +++++++++++--- 9 files changed, 204 insertions(+), 15 deletions(-) create mode 100644 src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonClassFields.cs create mode 100644 src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/PythonParserParseFieldTests.cs diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj b/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj index f7468c9d24..06dbede0cb 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.csproj @@ -94,6 +94,7 @@ + diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs index fad6d2aca5..1674f4095a 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs @@ -60,16 +60,19 @@ namespace ICSharpCode.PythonBinding currentClass = null; } - public override bool Walk(FunctionDefinition node) + public override bool Walk(FunctionDefinition functionDefinition) { - if (node.Body == null) { + if (functionDefinition.Body == null) { return false; } IClass c = GetClassBeingWalked(); - PythonMethodDefinition methodDefinition = new PythonMethodDefinition(node); - methodDefinition.CreateMethod(c); + PythonMethodDefinition methodDefinition = new PythonMethodDefinition(functionDefinition); + PythonMethod method = methodDefinition.CreateMethod(c); + if (method is PythonConstructor) { + FindFields(c, functionDefinition); + } return false; } @@ -97,6 +100,12 @@ namespace ICSharpCode.PythonBinding } } + void FindFields(IClass c, FunctionDefinition functionDefinition) + { + PythonClassFields fields = new PythonClassFields(functionDefinition); + fields.AddFields(c); + } + /// /// Walks an import statement and adds it to the compilation unit's /// Usings. @@ -118,13 +127,13 @@ namespace ICSharpCode.PythonBinding public override bool Walk(AssignmentStatement node) { if (currentClass != null) { - WalkPropertyAssignment(node); + FindProperty(node); return false; } return base.Walk(node); } - void WalkPropertyAssignment(AssignmentStatement node) + void FindProperty(AssignmentStatement node) { PythonPropertyAssignment propertyAssignment = new PythonPropertyAssignment(node); propertyAssignment.CreateProperty(currentClass); diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonClassFields.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonClassFields.cs new file mode 100644 index 0000000000..8b68bd21ea --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonClassFields.cs @@ -0,0 +1,64 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using ICSharpCode.SharpDevelop.Dom; +using IronPython.Compiler.Ast; + +namespace ICSharpCode.PythonBinding +{ + public class PythonClassFields : PythonWalker + { + FunctionDefinition functionDefinition; + IClass declaringType; + List fieldNamesAdded; + + public PythonClassFields(FunctionDefinition functionDefinition) + { + this.functionDefinition = functionDefinition; + } + + public void AddFields(IClass declaringType) + { + this.declaringType = declaringType; + fieldNamesAdded = new List(); + + functionDefinition.Body.Walk(this); + } + + public override bool Walk(AssignmentStatement node) + { + string fieldName = GetFieldName(node); + AddFieldToDeclaringType(fieldName); + return false; + } + + string GetFieldName(AssignmentStatement node) + { + string[] memberNames = PythonControlFieldExpression.GetMemberNames(node.Left[0] as MemberExpression); + return GetFieldName(memberNames); + } + + string GetFieldName(string[] memberNames) + { + if (memberNames.Length > 1) { + if (PythonSelfResolver.IsSelfExpression(memberNames[0])) { + return memberNames[1]; + } + } + return null; + } + + void AddFieldToDeclaringType(string fieldName) + { + if (fieldName != null) { + if (!fieldNamesAdded.Contains(fieldName)) { + DefaultField field = new DefaultField(declaringType, fieldName); + declaringType.Fields.Add(field); + fieldNamesAdded.Add(fieldName); + } + } + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonMemberResolver.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonMemberResolver.cs index 475fc17f62..408b680c8e 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonMemberResolver.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonMemberResolver.cs @@ -15,6 +15,7 @@ namespace ICSharpCode.PythonBinding PythonClassResolver classResolver; PythonLocalVariableResolver localVariableResolver; PythonResolverContext resolverContext; + PythonSelfResolver selfResolver = new PythonSelfResolver(); public PythonMemberResolver(PythonClassResolver classResolver, PythonLocalVariableResolver localVariableResolver) { @@ -54,6 +55,9 @@ namespace ICSharpCode.PythonBinding if (c != null) { return c; } + if (PythonSelfResolver.IsSelfExpression(className)) { + return FindClassFromSelfResolver(); + } return FindClassFromLocalVariableResolver(className); } @@ -72,6 +76,16 @@ namespace ICSharpCode.PythonBinding return null; } + IClass FindClassFromSelfResolver() + { + PythonResolverContext newContext = resolverContext.Clone("self"); + ResolveResult result = selfResolver.Resolve(newContext); + if (result != null) { + return result.ResolvedType.GetUnderlyingClass(); + } + return null; + } + ResolveResult CreateResolveResult(IMember member) { if (member != null) { diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonResolverContext.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonResolverContext.cs index 9b31decf27..c98cdfe6f3 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonResolverContext.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonResolverContext.cs @@ -300,5 +300,12 @@ namespace ICSharpCode.PythonBinding } return false; } + + public PythonResolverContext Clone(string newExpression) + { + ParseInformation parseInfo = new ParseInformation(compilationUnit); + ExpressionResult newExpressionResult = new ExpressionResult(newExpression); + return new PythonResolverContext(parseInfo, newExpressionResult, fileContent); + } } } diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonSelfResolver.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonSelfResolver.cs index ce356dd8da..2d223fc7b7 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonSelfResolver.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonSelfResolver.cs @@ -21,9 +21,14 @@ namespace ICSharpCode.PythonBinding return null; } + public static bool IsSelfExpression(string expression) + { + return expression == "self"; + } + bool IsSelfExpression(PythonResolverContext resolverContext) { - return resolverContext.Expression == "self"; + return IsSelfExpression(resolverContext.Expression); } ResolveResult CreateResolveResult(PythonResolverContext resolverContext) diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/PythonParserParseFieldTests.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/PythonParserParseFieldTests.cs new file mode 100644 index 0000000000..71d66ca1b2 --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/PythonParserParseFieldTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using ICSharpCode.PythonBinding; +using ICSharpCode.SharpDevelop.Dom; +using NUnit.Framework; +using PythonBinding.Tests.Utils; + +namespace PythonBinding.Tests.Parsing +{ + [TestFixture] + public class PythonParserParseFieldTests + { + IClass myClass; + + void ParseCode(string code) + { + ParseInformation parseInfo = PythonParserHelper.CreateParseInfo(code); + myClass = parseInfo.CompilationUnit.Classes[0]; + } + + [Test] + public void Parse_ClassHasOneFieldCalledCount_ReturnsParseInfoWithClassWithFieldCalledCount() + { + string code = + "class MyClass:\r\n" + + " def __init__(self):\r\n" + + " self._count = 0\r\n" + + "\r\n"; + + ParseCode(code); + IField field = myClass.Fields[0]; + string name = field.Name; + string expectedName = "_count"; + + Assert.AreEqual(expectedName, name); + } + + [Test] + public void Parse_ClassFieldInitialisedTwice_ReturnsParseInfoWithClassWithOnlyOneField() + { + string code = + "class MyClass:\r\n" + + " def __init__(self):\r\n" + + " self._count = 0\r\n" + + " self._count = 3\r\n" + + "\r\n"; + + ParseCode(code); + int howManyFields = myClass.Fields.Count; + int expectedNumberOfFields = 1; + + Assert.AreEqual(expectedNumberOfFields, howManyFields); + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj index f73928cf9a..4d53119545 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj @@ -343,6 +343,7 @@ + diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Resolver/ResolveSelfTests.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Resolver/ResolveSelfTests.cs index ec19928927..002295c74f 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Resolver/ResolveSelfTests.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Resolver/ResolveSelfTests.cs @@ -42,6 +42,18 @@ namespace PythonBinding.Tests.Resolver resolverHelper.Resolve("self"); } + void ResolveSelfMethodExpression() + { + string code = + "class Foo:\r\n" + + " def bar(self):\r\n" + + " return 0\r\n" + + "\r\n"; + + CreateResolver(code); + resolverHelper.Resolve("self.bar"); + } + [Test] public void Resolve_ExpressionIsSelf_ResolveResultCallingClassReturnsFooClass() { @@ -60,25 +72,44 @@ namespace PythonBinding.Tests.Resolver Assert.AreEqual("bar", methodName); } - void ResolveSelfMethodExpression() + [Test] + public void Resolve_ExpressionIsSelfFollowedByMethodCall_MethodGroupResolveResultContainingTypeUnderlyingClassIsFooClass() + { + ResolveSelfMethodExpression(); + IClass underlyingClass = resolverHelper.MethodGroupResolveResult.ContainingType.GetUnderlyingClass(); + + Assert.AreEqual(fooClass, underlyingClass); + } + + [Test] + public void Resolve_ClassPropertyReferencedThroughSelf_MemberResolveResultResolvedMemberIsNamePropertyOnFooClass() { string code = "class Foo:\r\n" + - " def bar(self):\r\n" + - " return 0\r\n" + + " def get_name(self):\r\n" + + " return 'test'\r\n" + + " name = property(fget=get_name)\r\n" + "\r\n"; CreateResolver(code); - resolverHelper.Resolve("self.bar"); + resolverHelper.Resolve("self.name"); + + IMember member = resolverHelper.MemberResolveResult.ResolvedMember; + IMember expectedMember = fooClass.Properties[0]; + + Assert.AreSame(expectedMember, member); } [Test] - public void Resolve_ExpressionIsSelfFollowedByMethodCall_MethodGroupResolveResultContainingTypeUnderlyingClassIsFooClass() + public void Resolve_PropertyReferencedThroughSelfButOutsideClass_ReturnsNull() { - ResolveSelfMethodExpression(); - IClass underlyingClass = resolverHelper.MethodGroupResolveResult.ContainingType.GetUnderlyingClass(); + string code = String.Empty; + resolverHelper = new PythonResolverTestsHelper(code); + resolverHelper.Resolve("self.name"); - Assert.AreEqual(fooClass, underlyingClass); + ResolveResult result = resolverHelper.ResolveResult; + + Assert.IsNull(result); } } }