QmlJS: Improve support for multiple imports into same alias

Add extra handling for aliased imports to avoid handling
shadowed imports as unknown which in turn ignored them
and marked their members as unknown types and did not
provide auto completion for their members.

Fixes: QTCREATORBUG-15684
Change-Id: Iee1009cbdfde13ce261854c3239b9b50c434f563
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Christian Stenger
2019-12-03 11:07:47 +01:00
parent e5f1559bb6
commit 8f682573a8
3 changed files with 74 additions and 36 deletions

View File

@@ -104,6 +104,9 @@ const Imports *Context::imports(const QmlJS::Document *doc) const
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId *qmlTypeName, const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId *qmlTypeName,
UiQualifiedId *qmlTypeNameEnd) const UiQualifiedId *qmlTypeNameEnd) const
{ {
if (!qmlTypeName)
return nullptr;
const Imports *importsObj = imports(doc); const Imports *importsObj = imports(doc);
if (!importsObj) if (!importsObj)
return nullptr; return nullptr;
@@ -111,8 +114,13 @@ const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId
if (!objectValue) if (!objectValue)
return nullptr; return nullptr;
for (UiQualifiedId *iter = qmlTypeName; objectValue && iter && iter != qmlTypeNameEnd; UiQualifiedId *iter = qmlTypeName;
iter = iter->next) { if (const ObjectValue *value = importsObj->aliased(iter->name.toString())) {
objectValue = value;
iter = iter->next;
}
for ( ; objectValue && iter && iter != qmlTypeNameEnd; iter = iter->next) {
const Value *value = objectValue->lookupMember(iter->name.toString(), this, nullptr, false); const Value *value = objectValue->lookupMember(iter->name.toString(), this, nullptr, false);
if (!value) if (!value)
return nullptr; return nullptr;
@@ -125,6 +133,9 @@ const ObjectValue *Context::lookupType(const QmlJS::Document *doc, UiQualifiedId
const ObjectValue *Context::lookupType(const QmlJS::Document *doc, const QStringList &qmlTypeName) const const ObjectValue *Context::lookupType(const QmlJS::Document *doc, const QStringList &qmlTypeName) const
{ {
if (qmlTypeName.isEmpty())
return nullptr;
const Imports *importsObj = imports(doc); const Imports *importsObj = imports(doc);
if (!importsObj) if (!importsObj)
return nullptr; return nullptr;
@@ -132,11 +143,14 @@ const ObjectValue *Context::lookupType(const QmlJS::Document *doc, const QString
if (!objectValue) if (!objectValue)
return nullptr; return nullptr;
foreach (const QString &name, qmlTypeName) { auto iter = qmlTypeName.cbegin();
if (!objectValue) if (const ObjectValue *value = importsObj->aliased(*iter)) {
return nullptr; objectValue = value;
++iter;
const Value *value = objectValue->lookupMember(name, this); }
auto end = qmlTypeName.cend();
for ( ; objectValue && iter != end; ) {
const Value *value = objectValue->lookupMember(*iter, this);
if (!value) if (!value)
return nullptr; return nullptr;

View File

@@ -2355,6 +2355,12 @@ TypeScope::TypeScope(const Imports *imports, ValueOwner *valueOwner)
const Value *TypeScope::lookupMember(const QString &name, const Context *context, const Value *TypeScope::lookupMember(const QString &name, const Context *context,
const ObjectValue **foundInObject, bool) const const ObjectValue **foundInObject, bool) const
{ {
if (const ObjectValue *value = m_imports->resolveAliasAndMarkUsed(name)) {
if (foundInObject)
*foundInObject = this;
return value;
}
const QList<Import> &imports = m_imports->all(); const QList<Import> &imports = m_imports->all();
for (int pos = imports.size(); --pos >= 0; ) { for (int pos = imports.size(); --pos >= 0; ) {
const Import &i = imports.at(pos); const Import &i = imports.at(pos);
@@ -2365,16 +2371,6 @@ const Value *TypeScope::lookupMember(const QString &name, const Context *context
if (info.type() == ImportType::File || info.type() == ImportType::QrcFile) if (info.type() == ImportType::File || info.type() == ImportType::QrcFile)
continue; continue;
if (!info.as().isEmpty()) {
if (info.as() == name) {
if (foundInObject)
*foundInObject = this;
i.used = true; // FIXME: This evilly modifies a 'const' object
return import;
}
continue;
}
if (const Value *v = import->lookupMember(name, context, foundInObject)) { if (const Value *v = import->lookupMember(name, context, foundInObject)) {
i.used = true; i.used = true;
return v; return v;
@@ -2418,26 +2414,10 @@ JSImportScope::JSImportScope(const Imports *imports, ValueOwner *valueOwner)
const Value *JSImportScope::lookupMember(const QString &name, const Context *, const Value *JSImportScope::lookupMember(const QString &name, const Context *,
const ObjectValue **foundInObject, bool) const const ObjectValue **foundInObject, bool) const
{ {
const QList<Import> &imports = m_imports->all(); const ObjectValue *value = m_imports->resolveAliasAndMarkUsed(name);
for (int pos = imports.size(); --pos >= 0; ) {
const Import &i = imports.at(pos);
const ObjectValue *import = i.object;
const ImportInfo &info = i.info;
// JS imports are always: import "somefile.js" as Foo
if (info.type() != ImportType::File && info.type() != ImportType::QrcFile)
continue;
if (info.as() == name) {
if (foundInObject)
*foundInObject = this;
i.used = true;
return import;
}
}
if (foundInObject) if (foundInObject)
*foundInObject = nullptr; *foundInObject = value ? this : nullptr;
return nullptr; return value;
} }
void JSImportScope::processMembers(MemberProcessor *processor) const void JSImportScope::processMembers(MemberProcessor *processor) const
@@ -2464,10 +2444,31 @@ Imports::Imports(ValueOwner *valueOwner)
, m_importFailed(false) , m_importFailed(false)
{} {}
class MemberCopy : public MemberProcessor
{
public:
explicit MemberCopy(ObjectValue *value) : m_value(value) {}
bool processProperty(const QString &name, const Value *value,
const PropertyInfo & /*propertyInfo*/) override
{
m_value->setMember(name, value);
return true;
}
private:
ObjectValue *m_value = nullptr;
};
void Imports::append(const Import &import) void Imports::append(const Import &import)
{ {
// when doing lookup, imports with 'as' clause are looked at first // when doing lookup, imports with 'as' clause are looked at first
if (!import.info.as().isEmpty()) { if (!import.info.as().isEmpty()) {
const QString alias = import.info.as();
if (!m_aliased.contains(alias))
m_aliased.insert(alias, m_typeScope->valueOwner()->newObject(nullptr));
ObjectValue *obj = m_aliased[alias];
MemberCopy copyProcessor(obj);
import.object->processMembers(&copyProcessor);
m_imports.append(import); m_imports.append(import);
} else { } else {
// find first as-import and prepend // find first as-import and prepend
@@ -2554,6 +2555,11 @@ const QList<Import> &Imports::all() const
return m_imports; return m_imports;
} }
const ObjectValue *Imports::aliased(const QString &name) const
{
return m_aliased.value(name, nullptr);
}
const TypeScope *Imports::typeScope() const const TypeScope *Imports::typeScope() const
{ {
return m_typeScope; return m_typeScope;
@@ -2564,6 +2570,20 @@ const JSImportScope *Imports::jsImportScope() const
return m_jsImportScope; return m_jsImportScope;
} }
const ObjectValue *Imports::resolveAliasAndMarkUsed(const QString &name) const
{
if (const ObjectValue *value = m_aliased.value(name, nullptr)) {
// mark all respective ImportInfo objects to avoid dropping imports (QmlDesigner) on rewrite
for (const Import &i : qAsConst(m_imports)) {
const ImportInfo &info = i.info;
if (info.as() == name)
i.used = true; // FIXME: This evilly modifies a 'const' object
}
return value;
}
return nullptr;
}
#ifdef QT_DEBUG #ifdef QT_DEBUG
class MemberDumper: public MemberProcessor class MemberDumper: public MemberProcessor

View File

@@ -1094,10 +1094,13 @@ public:
bool importFailed() const; bool importFailed() const;
const QList<Import> &all() const; const QList<Import> &all() const;
const ObjectValue *aliased(const QString &name) const;
const TypeScope *typeScope() const; const TypeScope *typeScope() const;
const JSImportScope *jsImportScope() const; const JSImportScope *jsImportScope() const;
const ObjectValue *resolveAliasAndMarkUsed(const QString &name) const;
#ifdef QT_DEBUG #ifdef QT_DEBUG
void dump() const; void dump() const;
#endif #endif
@@ -1106,6 +1109,7 @@ private:
// holds imports in the order they appeared, // holds imports in the order they appeared,
// lookup order is back to front // lookup order is back to front
QList<Import> m_imports; QList<Import> m_imports;
QHash<QString, ObjectValue *> m_aliased;
TypeScope *m_typeScope; TypeScope *m_typeScope;
JSImportScope *m_jsImportScope; JSImportScope *m_jsImportScope;
bool m_importFailed; bool m_importFailed;