Warn on quoted attributes#1601
Conversation
persistent/Database/Persist/Quasi.hs
Outdated
|
|
||
| @ | ||
| CREATE TABEL big_user_table ( | ||
| CREATE TABLE big_user_table ( |
persistent/Database/Persist/Quasi.hs
Outdated
| -> Vehicle' | ||
| convert (Entity _ (VehicycleBicycleSum _), Just (Entity _ (Bicycle brand)), _) = | ||
| convert (Entity _ (VehicleBicycleSum _), Just (Entity _ (Bicycle brand)), _) = | ||
| Bike brand |
There was a problem hiding this comment.
Two unrelated typo fixes
| , entityUniques = entityConstraintDefsUniquesList entityConstraintDefs | ||
| , entityForeigns = [] | ||
| , entityDerives = concat $ mapMaybe takeDerives textAttribs | ||
| , entityDerives = concat $ mapMaybe takeDerives (textFields ++ textDirectives) |
There was a problem hiding this comment.
To avoid changing the external interface, we'll just smush the field definitions and directives together for now. We should improve this in the future.
| foldMap | ||
| (maybe mempty (takeConstraint ps entNameHS cols) . NEL.nonEmpty) | ||
| textAttribs | ||
| (textFields ++ textDirectives) |
There was a problem hiding this comment.
To avoid changing the external interface, we'll just smush the field definitions and directives together for now. We should improve this in the future.
|
|
||
| -- | Attempts to parse with a provided parser. If it fails with an error matching | ||
| -- the provided predicate, it registers a delayed error with the provided message and falls | ||
| -- the provided predicate, it registers a delayed error and falls |
There was a problem hiding this comment.
This comment was inaccurate; the msg argument was actually unused. I'm not sure that's what we actually want, but fixing it seemed outside the scope of this PR.
| tokenContent = \case | ||
| Quotation s -> s | ||
| Equality l r -> mconcat [l, "=", r] | ||
| attributeContent :: Attribute -> Text |
There was a problem hiding this comment.
All of these fooContent methods exist in order to reserialize parsed data to text so it can be parsed again during code generation. I think this is silly, but removing it will be a pretty big refactor.
| Comment s -> s | ||
| DocComment s -> s | ||
|
|
||
| quotedAttributeErrorMessage :: String |
There was a problem hiding this comment.
We define a custom error message mostly so that we can fish it out again in tryOrReport, because it's otherwise not as easy as you'd think to determine when the quoted-attribute-forbidding parser has failed because of a quoted attribute as opposed to some other reason.
I don't love this but it works fine for now; a better solution would probably add 100+ LOC to this already-big PR.
| deriving (Show, Eq) | ||
|
|
||
| -- | Parses a Persistent-style type expression. | ||
| -- Persistent's type expressions are largely similar to Haskell's, but with a few differences: |
There was a problem hiding this comment.
I won't be surprised if, while I'm testing packages that depend on persistent, I find some places where people are depending on a less restricted set of types than this parser admits. I'll make fixes here as needed, but this is one reason why it would be really good to have a formal specification for the DSL.
532e65d to
bf5dd20
Compare
034a9bb to
10515fc
Compare
persistent/Database/Persist/Quasi.hs
Outdated
| -> Vehicle' | ||
| convert (Entity _ (VehicycleBicycleSum _), Just (Entity _ (Bicycle brand)), _) = | ||
| convert (Entity _ (VehicleBicycleSum _), Just (Entity _ (Bicycle brand)), _) = | ||
| Bike brand |
| typeExpr' :: ((MonadParsec e String) m) => Bool -> m TypeExpr | ||
| typeExpr' isInner = label "type expression" $ do |
There was a problem hiding this comment.
recommend creating a sum type instead of using bool here
There was a problem hiding this comment.
you're right; that's nicer
parsonsmatt
left a comment
There was a problem hiding this comment.
Code LGTM! Let's put a changelog/version bump in the relevant places (ie persistent-qq, persistent-test were both modified). patch should be fine for persistent-qq, minor bump for persistent-test
| -- | ||
| -- @since 2.17.1.0 | ||
| typeExprContent :: TypeExpr -> Text | ||
| typeExprContent = typeExprContent' False |
There was a problem hiding this comment.
Maybe reuse IsInner here?
| describe "type parsing" $ do | ||
| let | ||
| parseType :: String -> ParseResult TypeExpr | ||
| parseType s = do | ||
| let | ||
| (warnings, res) = runConfiguredParser defaultPersistSettings initialExtraState innerTypeExpr "" s | ||
| case res of | ||
| Left peb -> (warnings, Left peb) | ||
| Right (te, _acc) -> (warnings, Right te) | ||
|
|
||
| isType typeStr expectedTypeExpr = do | ||
| let (_warnings, Right te) = parseType typeStr | ||
| te `shouldBe` expectedTypeExpr | ||
| typeExprContent te `shouldBe` T.pack typeStr | ||
|
|
||
| -- these are some helper functions to make expectations less verbose | ||
| simpleType s = (TypeApplication (TypeConstructorExpr (TypeConstructor s)) []) | ||
| typeApp s ts = (TypeApplication (TypeConstructorExpr (TypeConstructor s)) ts) | ||
| listOf t = (TypeApplication (TypeConstructorExpr ListConstructor) [t]) |
There was a problem hiding this comment.
this appears to be mixing 2 and 4 space indent, run fourmolu pls?
(For now, let's put {- fourmolu disable -} comments in the files that are failing this
|
|
||
| it "parses types of kind '*'" $ do | ||
| "String" `isType` simpleType "String" | ||
|
|
||
| it "parses type constructors with dots" $ do | ||
| "ThisIs.AType" `isType` simpleType "ThisIs.AType" | ||
|
|
||
| it "parses higher-kinded types" $ do | ||
| "Maybe String" `isType` typeApp "Maybe" [simpleType "String"] | ||
|
|
||
| it "is greedy when parsing arguments to a type constructor" $ do | ||
| "Map String Int" `isType` typeApp "Map" [simpleType "String", simpleType "Int"] | ||
|
|
||
| it "parses higher-kinded types when parameterized by complex types (1)" $ do | ||
| "Map String (Maybe [Int])" `isType` | ||
| typeApp "Map" [simpleType "String", typeApp "Maybe" [listOf (simpleType "Int")]] | ||
|
|
||
| it "parses higher-kinded types when parameterized by complex types (2)" $ do | ||
| "Map (Maybe Int) [Int]" `isType` | ||
| typeApp "Map" [(typeApp "Maybe" [simpleType "Int"]), listOf (simpleType "Int")] |
| c <- char '!' <|> char '~' | ||
| case c of | ||
| '!' -> pure Strict | ||
| '~' -> pure Lazy | ||
| _ -> error "unreachable" |
There was a problem hiding this comment.
Can avoid error here
| c <- char '!' <|> char '~' | |
| case c of | |
| '!' -> pure Strict | |
| '~' -> pure Lazy | |
| _ -> error "unreachable" | |
| (Strict <$ char '!') <|> (Lazy <$ char '~') |
<$ is an operator that does:
(<$) :: (Functor f) => a -> f b -> f a
a <$ fb = fmap (const a) fbOr, monadically,
a <$ fb = do
_ <- fb
pure a| entityField :: Parser Member | ||
| entityField = do | ||
| dcb <- getDcb | ||
| pos <- getSourcePos | ||
| ss <- optional fieldStrictness | ||
| fn <- L.lexeme spaceConsumer fieldName | ||
| ft <- L.lexeme spaceConsumer typeExpr -- Note that `typeExpr` consumes outer parentheses. | ||
| fa <- optional $ L.lexeme spaceConsumer (many attribute) | ||
| _ <- setLastDocumentablePosition | ||
| lookAhead (void newline <|> eof) | ||
| pure $ | ||
| MemberEntityField | ||
| EntityField | ||
| { entityFieldDocCommentBlock = dcb | ||
| , entityFieldStrictness = ss | ||
| , entityFieldName = fn | ||
| , entityFieldType = ft | ||
| , entityFieldAttributes = fromMaybe [] fa | ||
| , entityFieldPos = pos | ||
| } |
There was a problem hiding this comment.
now that's a pretty parser 👏🏻
27dc131 to
6e595e5
Compare
For #1599.
The old version of the parser allowed entity field attributes to be wrapped in quotation marks; the enclosing
"s would get parsed away and the enclosed text passed through in the attribute text on theParsedEntityDefinition.I discussed this with @parsonsmatt , and we'd like to deprecate this behavior. In #1599 it seems to have been in use as a workaround for a parser bug that no longer exists. (If it's necessary to escape whitespace in a field attribute, this can still be done by wrapping the attribute in
()instead of quotes.)This PR restores the old behavior and adds a configurable deprecation message as a warning. It's not easy to do this without some further refactoring of the parser. Banning quotes in entity field arguments will also ban them in entity field types, which would be incorrect — field types can include typelevel string literals.
This is a good opportunity to make the parser smarter. It's currently pretty naive — essentially, it breaks each line of an entity definition block into tokens and then stops. In doing this, it ignores a lot of genuine syntactic data. It doesn't know that the line starts with the field name, is followed by a type, and is then followed by a series of attributes. It's very easy to write a field definition that parses successfully but fails semantically.
Improving this situation is an incremental directional step towards a formal specification for the language.
This PR:
derivingstatements. I'm calling these "directives" here. In the interest of keeping the PR from growing even more huge, directives are parsed naively for now.derivingsyntax in the future; this PR will make it easier to do that.Currently, all of this new structure is thrown away in
mkUnboundEntityDef. There's something of an impedance mismatch:mkUnboundEntityDefdoesn't care about the difference between field definitions and directives; it wants the parsed structure to be reduced back to a string of tokens.mkUnboundEntityDefwants to re-parse types itself.mkUnboundEntityDefwants to re-parse key/value attributes itself.I think this situation could be improved with some effort, but that's outside the scope of this PR.