blob: 770e8909159999fea46bdf267f9179fe9631fd97 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtXmlPatterns module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qargumentconverter_p.h"
#include "qatomictype_p.h"
#include "qatomizer_p.h"
#include "qbuiltintypes_p.h"
#include "qcardinalityverifier_p.h"
#include "qcommonsequencetypes_p.h"
#include "qfunctionfactory_p.h"
#include "qitemverifier_p.h"
#include "qpatternistlocale_p.h"
#include "quntypedatomicconverter_p.h"
#include "qtypechecker_p.h"
QT_BEGIN_NAMESPACE
using namespace QPatternist;
QString TypeChecker::wrongType(const NamePool::Ptr &np,
const ItemType::Ptr &reqType,
const ItemType::Ptr &opType)
{
return QtXmlPatterns::tr("Required type is %1, but %2 was found.")
.arg(formatType(np, reqType), formatType(np, opType));
}
Expression::Ptr TypeChecker::applyFunctionConversion(const Expression::Ptr &operand,
const SequenceType::Ptr &reqType,
const StaticContext::Ptr &context,
const ReportContext::ErrorCode code,
const Options options)
{
Q_ASSERT_X(!ReportContext::codeToString(code).isEmpty(), Q_FUNC_INFO,
"This test ensures 'code' exists, otherwise codeToString() would assert.");
Q_ASSERT(operand);
Q_ASSERT(reqType);
Q_ASSERT(context);
/* Do it in two steps: verify type, and then cardinality. */
const Expression::Ptr cardVerified(CardinalityVerifier::verifyCardinality(operand, reqType->cardinality(), context, code));
return verifyType(cardVerified, reqType, context, code, options);
}
bool TypeChecker::promotionPossible(const ItemType::Ptr &fromType,
const ItemType::Ptr &toType,
const StaticContext::Ptr &context)
{
/* These types can be promoted to xs:string. xs:untypedAtomic should be
* cast when interpreting it formally, but implementing it as a promotion
* gives the same result(and is faster). */
if(*toType == *BuiltinTypes::xsString &&
(BuiltinTypes::xsUntypedAtomic->xdtTypeMatches(fromType) ||
BuiltinTypes::xsAnyURI->xdtTypeMatches(fromType)))
return true;
if(*toType == *BuiltinTypes::xsDouble &&
BuiltinTypes::numeric->xdtTypeMatches(fromType))
{
/* Any numeric can be promoted to xs:double. */
return true;
}
/* xs:decimal/xs:integer can be promoted to xs:float. */
if(*toType == *BuiltinTypes::xsFloat && BuiltinTypes::xsDecimal->xdtTypeMatches(fromType))
{
context->warning(QtXmlPatterns::tr("Promoting %1 to %2 may cause loss of precision.")
.arg(formatType(context->namePool(), fromType))
.arg(formatType(context->namePool(), BuiltinTypes::xsFloat)));
return true;
}
return false;
}
Expression::Ptr TypeChecker::typeCheck(Expression *const op,
const StaticContext::Ptr &context,
const SequenceType::Ptr &reqType)
{
return Expression::Ptr(op->typeCheck(context, reqType));
}
Expression::Ptr TypeChecker::verifyType(const Expression::Ptr &operand,
const SequenceType::Ptr &reqSeqType,
const StaticContext::Ptr &context,
const ReportContext::ErrorCode code,
const Options options)
{
const ItemType::Ptr reqType(reqSeqType->itemType());
const Expression::Properties props(operand->properties());
/* If operand requires a focus, do the necessary type checking for that. */
if(props.testFlag(Expression::RequiresFocus) && options.testFlag(CheckFocus))
{
const ItemType::Ptr contextType(context->contextItemType());
if(contextType)
{
if(props.testFlag(Expression::RequiresContextItem))
{
Q_ASSERT_X(operand->expectedContextItemType(), Q_FUNC_INFO,
"When the Expression sets the RequiresContextItem property, it must "
"return a type in expectedContextItemType()");
const ItemType::Ptr expectedContextType(operand->expectedContextItemType());
/* Allow the empty sequence. We don't want to trigger XPTY0020 on ()/... . */
if(!expectedContextType->xdtTypeMatches(contextType) && contextType != CommonSequenceTypes::Empty)
{
context->error(wrongType(context->namePool(), operand->expectedContextItemType(), contextType),
ReportContext::XPTY0020, operand.data());
return operand;
}
}
}
else
{
context->error(QtXmlPatterns::tr("The focus is undefined."), ReportContext::XPDY0002, operand.data());
return operand;
}
}
SequenceType::Ptr operandSeqType(operand->staticType());
ItemType::Ptr operandType(operandSeqType->itemType());
/* This returns the operand if the types are identical or if operandType
* is a subtype of reqType. */
if(reqType->xdtTypeMatches(operandType) || *operandType == *CommonSequenceTypes::Empty)
return operand;
/* Since we haven't exited yet, it means that the operandType is a super type
* of reqType, and that there hence is a path down to it through the
* type hierachy -- but that doesn't necessarily mean that a up-cast(down the
* hierarchy) would succeed. */
Expression::Ptr result(operand);
if(reqType->isAtomicType())
{
const Expression::ID opID = operand->id();
if((opID == Expression::IDArgumentReference ||
(opID == Expression::IDCardinalityVerifier && operand->operands().first()->is(Expression::IDArgumentReference)))
&& *BuiltinTypes::item == *operandType)
return Expression::Ptr(new ArgumentConverter(result, reqType));
if(!operandType->isAtomicType())
{
result = Expression::Ptr(new Atomizer(result));
/* The atomizer might know more about the type. */
operandType = result->staticType()->itemType();
}
if(reqType->xdtTypeMatches(operandType))
{
/* Atomization was sufficient. Either the expected type is xs:anyAtomicType
* or the type the Atomizer knows it returns, matches the required type. */
return result;
}
const bool compatModeEnabled = context->compatModeEnabled();
if((options.testFlag(AutomaticallyConvert) && BuiltinTypes::xsUntypedAtomic->xdtTypeMatches(operandType)) ||
(compatModeEnabled && BuiltinTypes::xsString->xdtTypeMatches(reqType)))
{
if(*reqType == *BuiltinTypes::numeric)
{
result = typeCheck(new UntypedAtomicConverter(result, BuiltinTypes::xsDouble, code),
context, reqSeqType);
}
else
result = typeCheck(new UntypedAtomicConverter(result, reqType, code), context, reqSeqType);
/* The UntypedAtomicConverter might know more about the type, so reload. */
operandType = result->staticType()->itemType();
}
else if(compatModeEnabled && *reqType == *BuiltinTypes::xsDouble)
{
const FunctionFactory::Ptr functions(context->functionSignatures());
Expression::List numberArgs;
numberArgs.append(operand);
result = functions->createFunctionCall(QXmlName(StandardNamespaces::fn, StandardLocalNames::number),
numberArgs,
context,
operand.data())->typeCheck(context, reqSeqType);
operandType = result->staticType()->itemType();
context->wrapExpressionWith(operand.data(), result);
}
if(reqType->xdtTypeMatches(operandType))
return result;
/* Test if promotion will solve it; the xdtTypeMatches didn't
* do that. */
if(options.testFlag(AutomaticallyConvert) && promotionPossible(operandType, reqType, context))
{
if(options.testFlag(GeneratePromotion))
return Expression::Ptr(new UntypedAtomicConverter(result, reqType));
else
return result;
}
if(operandType->xdtTypeMatches(reqType))
{
/* For example, operandType is numeric, and reqType is xs:integer. */
return Expression::Ptr(new ItemVerifier(result, reqType, code));
}
else
{
context->error(wrongType(context->namePool(), reqType, operandType), code, operand.data());
return result;
}
}
else if(reqType->isNodeType())
{
ReportContext::ErrorCode myCode;
if(*reqType == *CommonSequenceTypes::EBV->itemType())
myCode = ReportContext::FORG0006;
else
myCode = code;
/* empty-sequence() is considered valid because it's ok to do
* for example nilled( () ). That is, to pass an empty sequence to a
* function requiring for example node()?. */
if(*operandType == *CommonSequenceTypes::Empty)
return result;
else if(!operandType->xdtTypeMatches(reqType))
{
context->error(wrongType(context->namePool(), reqType, operandType), myCode, operand.data());
return result;
}
/* Operand must be an item. Thus, the sequence can contain both
* nodes and atomic values: we have to verify. */
return Expression::Ptr(new ItemVerifier(result, reqType, myCode));
}
else
{
Q_ASSERT(*reqType == *CommonSequenceTypes::Empty);
/* element() doesn't match empty-sequence(), but element()* does. */
if(!reqType->xdtTypeMatches(operandType) &&
!operandSeqType->cardinality().allowsEmpty())
{
context->error(wrongType(context->namePool(), reqType, operandType),
code, operand.data());
return result;
}
}
/* This line should be reached if required type is
* EBVType, and the operand is compatible. */
return result;
}
QT_END_NAMESPACE