[flang] Analyze intrinsic and user-defined assignments
authorTim Keith <tkeith@nvidia.com>
Sat, 23 Nov 2019 00:46:11 +0000 (16:46 -0800)
committerTim Keith <tkeith@nvidia.com>
Tue, 26 Nov 2019 21:22:17 +0000 (13:22 -0800)
Change expression analysis to do assignment statements as it currently
does call statements. Check there for defined assignment and set
`typedAssignment` in the `AssignmentStmt` node to contain the analyzed
assignment, either intrinsic or user-defined.
When `var = expr` is implemented by subroutine `sub`, the analyzed
assignment contains a procedure reference to `sub(var, (expr))`.

Add `IsDefinedAssignment` to decide based on types and ranks of lhs
and rhs whether is can be a defined assignment. The result is
tri-state because when they are both the same derived type it can
be either intrinsic or defined. Use this where a similar decision
is made in `check-declarations.cc`.

Change "Procedure referenced in PURE subprogram" error message to
contain the name of the procedure. If the reference is from a defined
assignment that name won't appear on the highlighted source line.

Original-commit: flang-compiler/f18@5c87071210ec74e9395805dd547c107e7d3bf7b6
Reviewed-on: https://github.com/flang-compiler/f18/pull/841

flang/lib/semantics/check-declarations.cc
flang/lib/semantics/expression.cc
flang/lib/semantics/expression.h
flang/lib/semantics/tools.cc
flang/lib/semantics/tools.h
flang/test/semantics/CMakeLists.txt
flang/test/semantics/call10.f90
flang/test/semantics/resolve62.f90
flang/test/semantics/resolve63.f90
flang/test/semantics/resolve64.f90
flang/test/semantics/resolve66.f90 [new file with mode: 0644]

index 41ad4e8..29a5c0c 100644 (file)
@@ -450,18 +450,11 @@ void CheckHelper::SayNotDistinguishable(const SourceName &name,
   evaluate::AttachDeclaration(msg, proc2);
 }
 
-static bool ConflictsWithIntrinsicAssignment(
-    const DummyDataObject &arg0, const DummyDataObject &arg1) {
-  auto cat0{arg0.type.type().category()};
-  auto cat1{arg1.type.type().category()};
-  int rank0{arg0.type.Rank()};
-  int rank1{arg1.type.Rank()};
-  if (cat0 == TypeCategory::Derived || (rank1 > 0 && rank0 != rank1)) {
-    return false;
-  } else {
-    return cat0 == cat1 ||
-        (IsNumericTypeCategory(cat0) && IsNumericTypeCategory(cat1));
-  }
+static bool ConflictsWithIntrinsicAssignment(const Procedure &proc) {
+  auto lhs{std::get<DummyDataObject>(proc.dummyArguments[0].u).type};
+  auto rhs{std::get<DummyDataObject>(proc.dummyArguments[1].u).type};
+  return Tristate::No ==
+      IsDefinedAssignment(lhs.type(), lhs.Rank(), rhs.type(), rhs.Rank());
 }
 
 // Check if this procedure can be used for defined assignment (see 15.4.3.4.3).
@@ -476,9 +469,7 @@ bool CheckHelper::CheckDefinedAssignment(
   } else if (!CheckDefinedAssignmentArg(specific, proc.dummyArguments[0], 0) |
       !CheckDefinedAssignmentArg(specific, proc.dummyArguments[1], 1)) {
     return false;  // error was reported
-  } else if (ConflictsWithIntrinsicAssignment(
-                 std::get<DummyDataObject>(proc.dummyArguments[0].u),
-                 std::get<DummyDataObject>(proc.dummyArguments[1].u))) {
+  } else if (ConflictsWithIntrinsicAssignment(proc)) {
     msg = "Defined assignment subroutine '%s' conflicts with"
           " intrinsic assignment"_err_en_US;
   } else {
index 0a08912..f8a4301 100644 (file)
@@ -147,9 +147,12 @@ public:
     CHECK(!fatalErrors_);
     return std::move(actuals_);
   }
-  Expr<SomeType> GetAsExpr(std::size_t i) const {
+  const Expr<SomeType> &GetExpr(std::size_t i) const {
     return DEREF(actuals_.at(i).value().UnwrapExpr());
   }
+  Expr<SomeType> &&MoveExpr(std::size_t i) {
+    return std::move(DEREF(actuals_.at(i).value().UnwrapExpr()));
+  }
   void Analyze(const common::Indirection<parser::Expr> &x) {
     Analyze(x.value());
   }
@@ -157,6 +160,7 @@ public:
     actuals_.emplace_back(AnalyzeExpr(x));
     fatalErrors_ |= !actuals_.back();
   }
+  void Analyze(const parser::Variable &);
   void Analyze(const parser::ActualArgSpec &, bool isSubroutine);
 
   bool IsIntrinsicRelational(RelationalOperator) const;
@@ -172,18 +176,21 @@ public:
     return TryDefinedOp(
         context_.context().languageFeatures().GetNames(opr), std::move(msg));
   }
+  // Find and return a user-defined assignment
+  std::optional<ProcedureRef> TryDefinedAssignment();
+  std::optional<ProcedureRef> GetDefinedAssignmentProc();
 
 private:
   MaybeExpr TryDefinedOp(
       std::vector<const char *>, parser::MessageFixedText &&);
   std::optional<ActualArgument> AnalyzeExpr(const parser::Expr &);
   bool AreConformable() const;
-  const Symbol *FindDefinedOp(const char *) const;
+  Symbol *FindDefinedOp(const char *) const;
   std::optional<DynamicType> GetType(std::size_t) const;
   bool IsBOZLiteral(std::size_t i) const {
-    return std::holds_alternative<BOZLiteralConstant>(GetAsExpr(i).u);
+    return std::holds_alternative<BOZLiteralConstant>(GetExpr(i).u);
   }
-  void SayNoMatch(const char *);
+  void SayNoMatch(const std::string &, bool isAssignment = false);
   std::string TypeAsFortran(std::size_t);
   bool AnyUntypedOperand();
 
@@ -1785,6 +1792,18 @@ MaybeExpr ExpressionAnalyzer::AnalyzeCall(
   return std::nullopt;
 }
 
+void ExpressionAnalyzer::Analyze(const parser::AssignmentStmt &x) {
+  ArgumentAnalyzer analyzer{*this};
+  analyzer.Analyze(std::get<parser::Variable>(x.t));
+  analyzer.Analyze(std::get<parser::Expr>(x.t));
+  if (!analyzer.fatalErrors()) {
+    std::optional<ProcedureRef> procRef{analyzer.TryDefinedAssignment()};
+    x.typedAssignment.reset(new GenericAssignmentWrapper{procRef
+            ? Assignment{std::move(*procRef)}
+            : Assignment{analyzer.MoveExpr(0), analyzer.MoveExpr(1)}});
+  }
+}
+
 static bool IsExternalCalledImplicitly(
     parser::CharBlock callSite, const ProcedureDesignator &proc) {
   if (const auto *symbol{proc.GetSymbol()}) {
@@ -1815,8 +1834,8 @@ std::optional<characteristics::Procedure> ExpressionAnalyzer::CheckCall(
           pure{semantics::FindPureProcedureContaining(
               context_.FindScope(callSite))}) {
         Say(callSite,
-            "Procedure referenced in PURE subprogram '%s' must be PURE too"_err_en_US,
-            DEREF(pure->symbol()).name());
+            "Procedure '%s' referenced in PURE subprogram '%s' must be PURE too"_err_en_US,
+            DEREF(proc.GetSymbol()).name(), DEREF(pure->symbol()).name());
       }
     }
   }
@@ -1848,9 +1867,9 @@ static MaybeExpr NumericUnaryHelper(ExpressionAnalyzer &context,
     return std::nullopt;
   } else if (analyzer.IsIntrinsicNumeric(opr)) {
     if (opr == NumericOperator::Add) {
-      return analyzer.GetAsExpr(0);
+      return analyzer.MoveExpr(0);
     } else {
-      return Negation(context.GetContextualMessages(), analyzer.GetAsExpr(0));
+      return Negation(context.GetContextualMessages(), analyzer.MoveExpr(0));
     }
   } else {
     return analyzer.TryDefinedOp(AsFortran(opr),
@@ -1873,7 +1892,7 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::NOT &x) {
     return std::nullopt;
   } else if (analyzer.IsIntrinsicLogical()) {
     return AsGenericExpr(
-        LogicalNegation(std::get<Expr<SomeLogical>>(analyzer.GetAsExpr(0).u)));
+        LogicalNegation(std::get<Expr<SomeLogical>>(analyzer.MoveExpr(0).u)));
   } else {
     return analyzer.TryDefinedOp(LogicalOperator::Not,
         "Operand of %s must be LOGICAL; have %s"_err_en_US);
@@ -1923,7 +1942,7 @@ MaybeExpr NumericBinaryHelper(ExpressionAnalyzer &context, NumericOperator opr,
     return std::nullopt;
   } else if (analyzer.IsIntrinsicNumeric(opr)) {
     return NumericOperation<OPR>(context.GetContextualMessages(),
-        analyzer.GetAsExpr(0), analyzer.GetAsExpr(1),
+        analyzer.MoveExpr(0), analyzer.MoveExpr(1),
         context.GetDefaultKind(TypeCategory::Real));
   } else {
     return analyzer.TryDefinedOp(AsFortran(opr),
@@ -1978,8 +1997,8 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::Concat &x) {
             DIE("different types for intrinsic concat");
           }
         },
-        std::move(std::get<Expr<SomeCharacter>>(analyzer.GetAsExpr(0).u).u),
-        std::move(std::get<Expr<SomeCharacter>>(analyzer.GetAsExpr(1).u).u));
+        std::move(std::get<Expr<SomeCharacter>>(analyzer.MoveExpr(0).u).u),
+        std::move(std::get<Expr<SomeCharacter>>(analyzer.MoveExpr(1).u).u));
   } else {
     return analyzer.TryDefinedOp("//",
         "Operands of %s must be CHARACTER with the same kind; have %s and %s"_err_en_US);
@@ -2009,7 +2028,7 @@ MaybeExpr RelationHelper(ExpressionAnalyzer &context, RelationalOperator opr,
     return std::nullopt;
   } else if (analyzer.IsIntrinsicRelational(opr)) {
     return AsMaybeExpr(Relate(context.GetContextualMessages(), opr,
-        analyzer.GetAsExpr(0), analyzer.GetAsExpr(1)));
+        analyzer.MoveExpr(0), analyzer.MoveExpr(1)));
   } else {
     return analyzer.TryDefinedOp(opr,
         "Operands of %s must have comparable types; have %s and %s"_err_en_US);
@@ -2049,8 +2068,8 @@ MaybeExpr LogicalBinaryHelper(ExpressionAnalyzer &context, LogicalOperator opr,
     return std::nullopt;
   } else if (analyzer.IsIntrinsicLogical()) {
     return AsGenericExpr(BinaryLogicalOperation(opr,
-        std::get<Expr<SomeLogical>>(analyzer.GetAsExpr(0).u),
-        std::get<Expr<SomeLogical>>(analyzer.GetAsExpr(1).u)));
+        std::get<Expr<SomeLogical>>(analyzer.MoveExpr(0).u),
+        std::get<Expr<SomeLogical>>(analyzer.MoveExpr(1).u)));
   } else {
     return analyzer.TryDefinedOp(
         opr, "Operands of %s must be LOGICAL; have %s and %s"_err_en_US);
@@ -2396,6 +2415,15 @@ MaybeExpr ExpressionAnalyzer::MakeFunctionRef(
   }
 }
 
+void ArgumentAnalyzer::Analyze(const parser::Variable &x) {
+  source_.ExtendToCover(x.GetSource());
+  if (MaybeExpr expr{context_.Analyze(x)}) {
+    actuals_.emplace_back(std::move(*expr));
+  } else {
+    fatalErrors_ = true;
+  }
+}
+
 void ArgumentAnalyzer::Analyze(
     const parser::ActualArgSpec &arg, bool isSubroutine) {
   // TODO: C1002: Allow a whole assumed-size array to appear if the dummy
@@ -2491,7 +2519,7 @@ bool ArgumentAnalyzer::IsIntrinsicConcat() const {
 
 MaybeExpr ArgumentAnalyzer::TryDefinedOp(
     const char *opr, parser::MessageFixedText &&error) {
-  const Symbol *symbol{AnyUntypedOperand() ? nullptr : FindDefinedOp(opr)};
+  Symbol *symbol{AnyUntypedOperand() ? nullptr : FindDefinedOp(opr)};
   if (!symbol) {
     if (actuals_.size() == 1 || AreConformable()) {
       context_.Say(std::move(error), ToUpperCase(opr), TypeAsFortran(0),
@@ -2504,11 +2532,11 @@ MaybeExpr ArgumentAnalyzer::TryDefinedOp(
     return std::nullopt;
   }
   parser::Messages messages;
-  parser::Name name{source_, const_cast<Symbol *>(symbol)};
+  parser::Name name{source_, symbol};
   if (auto result{context_.AnalyzeDefinedOp(messages, name, GetActuals())}) {
     return result;
   } else {
-    SayNoMatch(opr);
+    SayNoMatch("OPERATOR(" + ToUpperCase(opr) + ')');
     return std::nullopt;
   }
 }
@@ -2523,6 +2551,43 @@ MaybeExpr ArgumentAnalyzer::TryDefinedOp(
   return TryDefinedOp(oprs[0], std::move(error));
 }
 
+std::optional<ProcedureRef> ArgumentAnalyzer::TryDefinedAssignment() {
+  using semantics::Tristate;
+  const Expr<SomeType> &lhs{GetExpr(0)};
+  const Expr<SomeType> &rhs{GetExpr(1)};
+  Tristate isDefined{semantics::IsDefinedAssignment(
+      lhs.GetType(), lhs.Rank(), rhs.GetType(), rhs.Rank())};
+  if (isDefined == Tristate::No) {
+    return std::nullopt;  // user-defined assignment not allowed for these args
+  }
+  auto restorer{context_.GetContextualMessages().SetLocation(source_)};
+  auto procRef{GetDefinedAssignmentProc()};
+  if (!procRef) {
+    if (isDefined == Tristate::Yes) {
+      SayNoMatch("ASSIGNMENT(=)", true);
+    }
+    return std::nullopt;
+  }
+  context_.CheckCall(source_, procRef->proc(), procRef->arguments());
+  return std::move(*procRef);
+}
+
+std::optional<ProcedureRef> ArgumentAnalyzer::GetDefinedAssignmentProc() {
+  parser::Messages tmpMessages;
+  auto restorer{context_.GetContextualMessages().SetMessages(tmpMessages)};
+  const auto &scope{context_.context().FindScope(source_)};
+  if (const Symbol *
+      symbol{scope.FindSymbol(parser::CharBlock{"assignment(=)"s})}) {
+    const Symbol *specific{context_.ResolveGeneric(*symbol, actuals_)};
+    if (specific) {
+      ProcedureDesignator designator{*specific};
+      actuals_[1]->Parenthesize();
+      return ProcedureRef{std::move(designator), std::move(actuals_)};
+    }
+  }
+  return std::nullopt;
+}
+
 std::optional<ActualArgument> ArgumentAnalyzer::AnalyzeExpr(
     const parser::Expr &expr) {
   source_.ExtendToCover(expr.source);
@@ -2541,7 +2606,7 @@ bool ArgumentAnalyzer::AreConformable() const {
   return evaluate::AreConformable(*actuals_[0], *actuals_[1]);
 }
 
-const Symbol *ArgumentAnalyzer::FindDefinedOp(const char *opr) const {
+Symbol *ArgumentAnalyzer::FindDefinedOp(const char *opr) const {
   const auto &scope{context_.context().FindScope(source_)};
   return scope.FindSymbol(parser::CharBlock{"operator("s + opr + ')'});
 }
@@ -2551,28 +2616,40 @@ std::optional<DynamicType> ArgumentAnalyzer::GetType(std::size_t i) const {
 }
 
 // Report error resolving opr when there is a user-defined one available
-void ArgumentAnalyzer::SayNoMatch(const char *opr) {
+void ArgumentAnalyzer::SayNoMatch(const std::string &opr, bool isAssignment) {
+  std::string type0{TypeAsFortran(0)};
   auto rank0{actuals_[0]->Rank()};
   if (actuals_.size() == 1) {
     if (rank0 > 0) {
-      context_.Say("No user-defined or intrinsic %s operator matches "
+      context_.Say("No intrinsic or user-defined %s matches "
                    "rank %d array of %s"_err_en_US,
-          ToUpperCase(opr), rank0, TypeAsFortran(0));
+          opr, rank0, type0);
     } else {
-      context_.Say("No user-defined or intrinsic %s operator matches "
+      context_.Say("No intrinsic or user-defined %s matches "
                    "operand type %s"_err_en_US,
-          ToUpperCase(opr), TypeAsFortran(0));
+          opr, type0);
     }
   } else {
+    std::string type1{TypeAsFortran(1)};
     auto rank1{actuals_[1]->Rank()};
     if (rank0 > 0 && rank1 > 0 && rank0 != rank1) {
-      context_.Say("No user-defined or intrinsic %s operator matches "
+      context_.Say("No intrinsic or user-defined %s matches "
                    "rank %d array of %s and rank %d array of %s"_err_en_US,
-          ToUpperCase(opr), rank0, TypeAsFortran(0), rank1, TypeAsFortran(1));
+          opr, rank0, type0, rank1, type1);
+    } else if (isAssignment && rank0 != rank1) {
+      if (rank0 == 0) {
+        context_.Say("No intrinsic or user-defined %s matches "
+                     "scalar %s and rank %d array of %s"_err_en_US,
+            opr, type0, rank1, type1);
+      } else {
+        context_.Say("No intrinsic or user-defined %s matches "
+                     "rank %d array of %s and scalar %s"_err_en_US,
+            opr, rank0, type0, type1);
+      }
     } else {
-      context_.Say("No user-defined or intrinsic %s operator matches "
+      context_.Say("No intrinsic or user-defined %s matches "
                    "operand types %s and %s"_err_en_US,
-          ToUpperCase(opr), TypeAsFortran(0), TypeAsFortran(1));
+          opr, type0, type1);
     }
   }
 }
@@ -2614,6 +2691,11 @@ void AnalyzeCallStmt(SemanticsContext &context, const parser::CallStmt &call) {
   evaluate::ExpressionAnalyzer{context}.Analyze(call);
 }
 
+void AnalyzeAssignmentStmt(
+    SemanticsContext &context, const parser::AssignmentStmt &stmt) {
+  evaluate::ExpressionAnalyzer{context}.Analyze(stmt);
+}
+
 ExprChecker::ExprChecker(SemanticsContext &context) : context_{context} {}
 
 bool ExprChecker::Walk(const parser::Program &program) {
index b112594..0c108c3 100644 (file)
@@ -237,6 +237,7 @@ public:
   MaybeExpr Analyze(const parser::StructureComponent &);
 
   void Analyze(const parser::CallStmt &);
+  void Analyze(const parser::AssignmentStmt &);
 
 protected:
   int IntegerTypeSpecKind(const parser::IntegerTypeSpec &);
@@ -383,6 +384,7 @@ evaluate::Expr<evaluate::SubscriptInteger> AnalyzeKindSelector(
     const std::optional<parser::KindSelector> &);
 
 void AnalyzeCallStmt(SemanticsContext &, const parser::CallStmt &);
+void AnalyzeAssignmentStmt(SemanticsContext &, const parser::AssignmentStmt &);
 
 // Semantic analysis of all expressions in a parse tree, which becomes
 // decorated with typed representations for top-level expressions.
@@ -406,6 +408,10 @@ public:
     AnalyzeCallStmt(context_, x);
     return false;
   }
+  bool Pre(const parser::AssignmentStmt &x) {
+    AnalyzeAssignmentStmt(context_, x);
+    return false;
+  }
 
   template<typename A> bool Pre(const parser::Scalar<A> &x) {
     AnalyzeExpr(context_, x);
index 0868d74..eb2495c 100644 (file)
@@ -82,6 +82,27 @@ const Scope *FindPureProcedureContaining(const Scope &start) {
   return nullptr;
 }
 
+Tristate IsDefinedAssignment(
+    const std::optional<evaluate::DynamicType> &lhsType, int lhsRank,
+    const std::optional<evaluate::DynamicType> &rhsType, int rhsRank) {
+  if (!lhsType || !rhsType) {
+    return Tristate::No;  // error or rhs is untyped
+  }
+  TypeCategory lhsCat{lhsType->category()};
+  TypeCategory rhsCat{rhsType->category()};
+  if (rhsRank > 0 && lhsRank != rhsRank) {
+    return Tristate::Yes;
+  } else if (lhsCat != TypeCategory::Derived) {
+    return ToTristate(lhsCat != rhsCat &&
+        (!IsNumericTypeCategory(lhsCat) || !IsNumericTypeCategory(rhsCat)));
+  } else if (rhsCat == TypeCategory::Derived &&
+      lhsType->GetDerivedTypeSpec() == rhsType->GetDerivedTypeSpec()) {
+    return Tristate::Maybe;  // TYPE(t) = TYPE(t) can be defined or intrinsic
+  } else {
+    return Tristate::Yes;
+  }
+}
+
 bool IsGenericDefinedOp(const Symbol &symbol) {
   const auto *details{symbol.GetUltimate().detailsIf<GenericDetails>()};
   return details && details->kind().IsDefinedOperator();
index 7f38608..e9fb90e 100644 (file)
@@ -23,6 +23,7 @@
 #include "semantics.h"
 #include "../common/Fortran.h"
 #include "../evaluate/expression.h"
+#include "../evaluate/type.h"
 #include "../evaluate/variable.h"
 #include "../parser/message.h"
 #include "../parser/parse-tree.h"
@@ -57,6 +58,14 @@ const DeclTypeSpec *FindParentTypeSpec(const Symbol &);
 // Return the Symbol of the variable of a construct association, if it exists
 const Symbol *GetAssociationRoot(const Symbol &);
 
+enum class Tristate { No, Yes, Maybe };
+inline Tristate ToTristate(bool x) { return x ? Tristate::Yes : Tristate::No; }
+
+// Is this a user-defined assignment? If both sides are the same derived type
+// (and the ranks are okay) the answer is Maybe.
+Tristate IsDefinedAssignment(
+    const std::optional<evaluate::DynamicType> &lhsType, int lhsRank,
+    const std::optional<evaluate::DynamicType> &rhsType, int rhsRank);
 bool IsGenericDefinedOp(const Symbol &);
 bool IsCommonBlockContaining(const Symbol &block, const Symbol &object);
 bool DoesScopeContain(const Scope *maybeAncestor, const Scope &maybeDescendent);
index 2db2eb1..741a9a6 100644 (file)
@@ -102,6 +102,7 @@ set(ERROR_TESTS
   resolve63.f90
   resolve64.f90
   resolve65.f90
+  resolve66.f90
   stop01.f90
   structconst01.f90
   structconst02.f90
index 4073893..f15cd60 100644 (file)
@@ -146,7 +146,7 @@ module m
   ! C1594 is tested in call12.f90.
   pure subroutine s10 ! C1595
     integer :: n
-    !ERROR: Procedure referenced in PURE subprogram 's10' must be PURE too
+    !ERROR: Procedure 'notpure' referenced in PURE subprogram 's10' must be PURE too
     n = notpure(1)
   end subroutine
   pure subroutine s11(to) ! C1596
index 89e6584..ac23c42 100644 (file)
@@ -40,7 +40,8 @@ subroutine s2
   real :: x, y(10), z
   logical :: a
   a = f(1.0)
-  a = f(y)  !TODO: this should resolve to f2 -- should get error here
+  !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types LOGICAL(4) and REAL(4)
+  a = f(y)
 end
 
 ! Resolve named operator
index f1c5614..4a32f1d 100644 (file)
@@ -61,36 +61,36 @@ contains
   subroutine test_relational()
     l = x == y  !OK
     l = x .eq. y  !OK
-    !ERROR: No user-defined or intrinsic == operator matches operand types TYPE(t) and REAL(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(==) matches operand types TYPE(t) and REAL(4)
     l = x == r
   end
   subroutine test_numeric()
     l = x + r  !OK
-    !ERROR: No user-defined or intrinsic + operator matches operand types REAL(4) and TYPE(t)
+    !ERROR: No intrinsic or user-defined OPERATOR(+) matches operand types REAL(4) and TYPE(t)
     l = r + x
   end
   subroutine test_logical()
     l = x .and. r  !OK
-    !ERROR: No user-defined or intrinsic .AND. operator matches operand types REAL(4) and TYPE(t)
+    !ERROR: No intrinsic or user-defined OPERATOR(.AND.) matches operand types REAL(4) and TYPE(t)
     l = r .and. x
   end
   subroutine test_unary()
     l = +x  !OK
-    !ERROR: No user-defined or intrinsic + operator matches operand type LOGICAL(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(+) matches operand type LOGICAL(4)
     l = +l
     l = .not. r  !OK
-    !ERROR: No user-defined or intrinsic .NOT. operator matches operand type TYPE(t)
+    !ERROR: No intrinsic or user-defined OPERATOR(.NOT.) matches operand type TYPE(t)
     l = .not. x
   end
   subroutine test_concat()
     l = x // y  !OK
-    !ERROR: No user-defined or intrinsic // operator matches operand types TYPE(t) and REAL(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(//) matches operand types TYPE(t) and REAL(4)
     l = x // r
   end
   subroutine test_conformability(x, y)
     real :: x(10), y(10,10)
     l = x + y  !OK
-    !ERROR: No user-defined or intrinsic + operator matches rank 2 array of REAL(4) and rank 1 array of REAL(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(+) matches rank 2 array of REAL(4) and rank 1 array of REAL(4)
     l = y + x
   end
 end
@@ -201,7 +201,7 @@ contains
   subroutine s1(x, y, z)
     logical :: x
     complex :: y, z
-    !ERROR: No user-defined or intrinsic .AND. operator matches operand types COMPLEX(4) and COMPLEX(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(.AND.) matches operand types COMPLEX(4) and COMPLEX(4)
     x = y .and. z
     !ERROR: No specific procedure of generic operator '.a.' matches the actual arguments
     x = y .a. z
index 6e5ba7c..5df1122 100644 (file)
@@ -51,9 +51,9 @@ contains
   subroutine s1(x, y, z)
     logical :: x
     complex :: y, z
-    !ERROR: No user-defined or intrinsic .A. operator matches operand types COMPLEX(4) and COMPLEX(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(.A.) matches operand types COMPLEX(4) and COMPLEX(4)
     x = y .and. z
-    !ERROR: No user-defined or intrinsic .A. operator matches operand types COMPLEX(4) and COMPLEX(4)
+    !ERROR: No intrinsic or user-defined OPERATOR(.A.) matches operand types COMPLEX(4) and COMPLEX(4)
     x = y .a. z
   end
 end
diff --git a/flang/test/semantics/resolve66.f90 b/flang/test/semantics/resolve66.f90
new file mode 100644 (file)
index 0000000..4c0a257
--- /dev/null
@@ -0,0 +1,119 @@
+! Copyright (c) 2019, NVIDIA CORPORATION.  All rights reserved.
+!
+! 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.
+
+! Test that user-defined assignment is used in the right places
+
+module m1
+  type t1
+  end type
+  type t2
+  end type
+  interface assignment(=)
+    subroutine assign_il(x, y)
+      integer, intent(out) :: x
+      logical, intent(in) :: y
+    end
+    subroutine assign_li(x, y)
+      logical, intent(out) :: x
+      integer, intent(in) :: y
+    end
+    subroutine assign_tt(x, y)
+      import t1
+      type(t1), intent(out) :: x
+      type(t1), intent(in) :: y
+    end
+    subroutine assign_tz(x, y)
+      import t1
+      type(t1), intent(out) :: x
+      complex, intent(in) :: y
+    end
+    subroutine assign_01(x, y)
+      real, intent(out) :: x
+      real, intent(in) :: y(:)
+    end
+  end interface
+contains
+  ! These are all intrinsic assignments
+  pure subroutine test1()
+    type(t2) :: a, b, b5(5)
+    logical :: l
+    integer :: i, i5(5)
+    a = b
+    b5 = a
+    l = .true.
+    i = z'1234'
+    i5 = 1.0
+  end
+
+  ! These have invalid type combinations
+  subroutine test2()
+    type(t1) :: a
+    type(t2) :: b
+    logical :: l, l5(5)
+    complex :: z, z5(5), z55(5,5)
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(t1) and TYPE(t2)
+    a = b
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types REAL(4) and LOGICAL(4)
+    r = l
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types LOGICAL(4) and REAL(4)
+    l = r
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(t1) and REAL(4)
+    a = r
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(t2) and COMPLEX(4)
+    b = z
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches scalar COMPLEX(4) and rank 1 array of COMPLEX(4)
+    z = z5
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches rank 1 array of LOGICAL(4) and scalar COMPLEX(4)
+    l5 = z
+    !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches rank 1 array of COMPLEX(4) and rank 2 array of COMPLEX(4)
+    z5 = z55
+  end
+
+  ! These should all be defined assignments. Because the subroutines
+  ! implementing them are not pure, they should all produce errors
+  pure subroutine test3()
+    type(t1) :: a, b
+    integer :: i
+    logical :: l
+    complex :: z
+    real :: r, r5(5)
+    !ERROR: Procedure 'assign_tt' referenced in PURE subprogram 'test3' must be PURE too
+    a = b
+    !ERROR: Procedure 'assign_il' referenced in PURE subprogram 'test3' must be PURE too
+    i = l
+    !ERROR: Procedure 'assign_li' referenced in PURE subprogram 'test3' must be PURE too
+    l = i
+    !ERROR: Procedure 'assign_il' referenced in PURE subprogram 'test3' must be PURE too
+    i = .true.
+    !ERROR: Procedure 'assign_tz' referenced in PURE subprogram 'test3' must be PURE too
+    a = z
+    !ERROR: Procedure 'assign_01' referenced in PURE subprogram 'test3' must be PURE too
+    r = r5
+  end
+
+  ! Like test3 but not in a pure subroutine so no errors.
+  subroutine test4()
+    type(t1) :: a, b
+    integer :: i
+    logical :: l
+    complex :: z
+    real :: r, r5(5)
+    a = b
+    i = l
+    l = i
+    i = .true.
+    a = z
+    r = r5
+  end
+end