blob: 59cc2874316751b5d438372951ef5dae476a1387 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.impl;
import com.intellij.psi.PsiFile;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.ResolveProcessor;
import com.jetbrains.python.toolbox.Maybe;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Something that describes a property, with all related accessors.
* <br/>
* User: dcheryasov
* Date: Jun 3, 2010 2:07:48 PM
*/
public abstract class PropertyBunch<MType> {
protected Maybe<MType> myGetter;
protected Maybe<MType> mySetter;
protected Maybe<MType> myDeleter;
protected String myDoc;
protected PyTargetExpression mySite;
@NotNull
public Maybe<MType> getGetter() {
return myGetter;
}
@NotNull
public Maybe<MType> getSetter() {
return mySetter;
}
@NotNull
public Maybe<MType> getDeleter() {
return myDeleter;
}
@Nullable
public String getDoc() {
return myDoc;
}
/**
* @param ref a reference as an argument in property() call
* @return value we want to store (resolved callable, name, etc)
*/
@NotNull
protected abstract Maybe<MType> translate(@Nullable PyExpression ref);
@Nullable
public static PyCallExpression findPropertyCallSite(@Nullable PyExpression source) {
if (source instanceof PyCallExpression) {
final PyCallExpression call = (PyCallExpression)source;
PyExpression callee = call.getCallee();
if (callee instanceof PyReferenceExpression) {
PyReferenceExpression ref = (PyReferenceExpression)callee;
if (ref.isQualified()) return null;
if (PyNames.PROPERTY.equals(callee.getName())) {
PsiFile file = source.getContainingFile();
if (isBuiltinFile(file) || !resolvesLocally(ref)) {
// we assume that a non-local name 'property' is a built-in name.
// ref.resolve() is not used because we run in stub building phase where resolve() is frowned upon.
// NOTE: this logic fails if (quite unusually) name 'property' is directly imported from builtins.
return call;
}
}
}
}
return null;
}
private static boolean isBuiltinFile(PsiFile file) {
final String name = file.getName();
return PyBuiltinCache.BUILTIN_FILE.equals(name) || PyBuiltinCache.BUILTIN_FILE_3K.equals(name);
}
/**
* Resolve in containing file only.
* @param ref what to resolve
* @return true iff ref obviously resolves to a local name (maybe partially, e.g. via import).
*/
protected static boolean resolvesLocally(@NotNull PyReferenceExpression ref) {
final String name = ref.getName();
if (name != null) {
final ResolveProcessor processor = new ResolveProcessor(name);
processor.setLocalResolve();
PyResolveUtil.scopeCrawlUp(processor, ref, name, null);
return processor.getResult() != null;
}
return false;
}
/**
* Tries to form a bunch from data available at a possible property() call site.
* @param source should be a PyCallExpression (if not, null is immediately returned).
* @param target what to fill with data (return type contravariance prevents us from creating it inside).
* @return true if target was successfully filled.
*/
protected static <MType> boolean fillFromCall(PyExpression source, PropertyBunch<MType> target) {
PyCallExpression call = findPropertyCallSite(source);
if (call != null) {
PyArgumentList arglist = call.getArgumentList();
if (arglist != null) {
PyExpression[] accessors = new PyExpression[3];
String doc = null;
int position = 0;
String[] keywords = new String[] { "fget", "fset", "fdel", "doc" };
for (PyExpression arg: arglist.getArguments()) {
int index = -1;
if (arg instanceof PyKeywordArgument) {
String keyword = ((PyKeywordArgument)arg).getKeyword();
index = ArrayUtil.indexOf(keywords, keyword);
if (index < 0) {
continue;
}
position = -1;
}
else if (position >= 0) {
index = position;
position++;
}
if (index >= 0) {
arg = PyUtil.peelArgument(arg);
if (index < 3) {
accessors [index] = arg;
}
else if (index == 3 && arg instanceof PyStringLiteralExpression) {
doc = ((PyStringLiteralExpression)arg).getStringValue();
}
}
}
target.myGetter = target.translate(accessors[0]);
target.mySetter = target.translate(accessors[1]);
target.myDeleter = target.translate(accessors[2]);
target.myDoc = doc;
return true;
}
}
return false;
}
}